簡(jiǎn)介
BLE(Bluetooth Low Energy),低功耗藍(lán)牙,使用2.4GHz無(wú)線(xiàn)電頻率。從藍(lán)牙4.0開(kāi)始支持。通常我們的手機(jī)都已支持藍(lán)牙4.0,iPhone從iPhone4s起已經(jīng)支持藍(lán)牙4.0(不是iPhone4s就是iPhone5,具體沒(méi)仔細(xì)查),而從iPhone8開(kāi)始,藍(lán)牙已經(jīng)使用5.0了,可以支持藍(lán)牙m(xù)esh。
宣告與發(fā)現(xiàn)
BLE設(shè)備通過(guò)廣播宣告(advertising)數(shù)據(jù)包的方式被發(fā)現(xiàn),發(fā)現(xiàn)設(shè)備的等待時(shí)間存在概率性,取決于三個(gè)參數(shù)(宣告間隔、掃描間隔和掃描窗口)。對(duì)于iOS開(kāi)發(fā),并不需要關(guān)心這幾個(gè)參數(shù),系統(tǒng)底層已經(jīng)為我們做了最佳的選擇。
通信
藍(lán)牙技術(shù)聯(lián)盟沿用經(jīng)典藍(lán)牙的規(guī)范內(nèi)容,為藍(lán)牙低功耗定義了一些profile,這些profile定義了一個(gè)設(shè)備在特定應(yīng)用情景下如何工作。profile都基于通用屬性規(guī)范(GATT)。GATT定義了屬性,作為通用的封裝數(shù)據(jù)的單位,并定義了如何通過(guò)藍(lán)牙連接傳輸屬性從而達(dá)到傳輸數(shù)據(jù)的目的。
對(duì)于手機(jī)端來(lái)說(shuō),藍(lán)牙設(shè)備的數(shù)據(jù)簡(jiǎn)單來(lái)說(shuō)就是一堆UUID及其對(duì)應(yīng)的值,iOS就是一堆CBUUID。
Exp:
將一個(gè)WiFi+BLE設(shè)備接入家庭WiFi,共分幾步:
- 發(fā)現(xiàn)設(shè)備
- 獲取設(shè)備信息,即獲取設(shè)備的services和characters
- 連接設(shè)備
- 與設(shè)備交互信息
- 斷開(kāi)連接
-
發(fā)現(xiàn)設(shè)備
//在你需要的地方初始化
//這里創(chuàng)建了一個(gè)串行隊(duì)列,如果queue為nil,則使用主線(xiàn)程
//實(shí)際使用時(shí)globle_queue也沒(méi)出現(xiàn)過(guò)問(wèn)題
self.serial_queue = dispatch_queue_create("ble_queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.serial_queue options:nil];
//開(kāi)始掃描
/* warn:掃描接口要在下面回調(diào)中調(diào)用,而不是初始化之后就可以調(diào)用。
在初始化之后立刻調(diào)用的話(huà),由于當(dāng)前central.state == CBManagerStateUnknown,會(huì)導(dǎo)致掃描失敗
*/
//實(shí)現(xiàn)CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
if (central.state == CBManagerStatePoweredOn) {
//在這里啟動(dòng)掃描接口,nil表示獲取所有services
/*CBCentralManagerScanOptionAllowDuplicatesKey
YES表示只要收到一個(gè)BLE設(shè)備的廣播包就會(huì)上報(bào),不會(huì)對(duì)相同的BLE設(shè)備進(jìn)行過(guò)濾,這樣做是比較耗電的,但業(yè)務(wù)需求,要不斷更新周?chē)腂LE設(shè)備,所以這里還是選擇了YES
NO為默認(rèn)值,同一個(gè)BLE設(shè)備的廣播包會(huì)被合并成,只通知一次事件
*/
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}];
}
}
//掃描到外圍設(shè)備時(shí)回調(diào)此方法
//外圍設(shè)備:建立連接過(guò)程中接受建立一個(gè)活躍的物理連接請(qǐng)求的LE設(shè)備定義為Peripheral 角色
//中心設(shè)備:建立連接過(guò)程中發(fā)起建立活躍物理連接請(qǐng)求的LE 設(shè)備定義為Central 角色
//在我們這個(gè)例子中手機(jī)就是中心設(shè)備,要接入家庭WiFi的這個(gè)設(shè)備就是外圍設(shè)備
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
//在該方法中,可以通過(guò)advertisementData解析過(guò)濾出自己的設(shè)備
//RSSI為信號(hào)強(qiáng)度,負(fù)數(shù),值越大(越接近0),信號(hào)越好
//找到自己要連接的設(shè)備后,可以將peripheral存起來(lái),用于后面在某個(gè)點(diǎn)擊事件中進(jìn)行連接
}
-
連接設(shè)備
//property getter
//我們只獲取下面兩個(gè)services
- (NSArray<CBUUID *> *)serviceUUIDs {
if (_serviceUUIDs == nil) {
//這里遇到過(guò)一個(gè)偶現(xiàn)的奇葩問(wèn)題
//128bit初始化uuid時(shí)偶現(xiàn)過(guò)崩潰,原因未知,所以使用16bit初始化
CBUUID* uuid1 = [CBUUID UUIDWithString:@"FF00"];
CBUUID* uuid2 = [CBUUID UUIDWithString:@"FF10"];
_serviceUUIDs = @[uuid1, uuid2];
}
return _serviceUUIDs;
}
//在你合適的地方調(diào)用連接外圍設(shè)備
self.centralManager connectPeripheral:self.peripheral options:nil];
//實(shí)現(xiàn)CBCentralManagerDelegate
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
//獲取感興趣的服務(wù)及屬性
[peripheral discoverServices:self.serviceUUIDs];
}
//實(shí)現(xiàn)CBPeripheralDelegate
//發(fā)現(xiàn)所有你要的服務(wù)后會(huì)回調(diào)該方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
//獲取該屬性對(duì)應(yīng)的服務(wù)
for (int i = 0; i < peripheral.services.count; i++) {
CBService *service = peripheral.services[i];
[peripheral discoverCharacteristics:nil forService:service];
}
}
//發(fā)現(xiàn)該服務(wù)對(duì)應(yīng)的屬性時(shí)會(huì)回調(diào)該方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error {
//這里我是做了個(gè)標(biāo)記為,當(dāng)獲取到了所有我想要的屬性之后,再開(kāi)始讀寫(xiě)操作
//但這里對(duì)我是不方便的,業(yè)務(wù)需要我全部獲取完之后才能讀寫(xiě),所以不能每次回調(diào)進(jìn)來(lái)都進(jìn)行相應(yīng)的操作
if(discoverEnd) {
//這里就可以按照自己的協(xié)議進(jìn)行讀寫(xiě)了
...write(ssid+password)
...
}
}
-
讀寫(xiě)設(shè)備
//實(shí)現(xiàn)相應(yīng)的delegate
//讀回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
//這里有一點(diǎn)比較奇怪的是,我對(duì)某一屬性setNotifyValue
//只有第一次是通過(guò)didUpdateNotificationStateForCharacteristic這個(gè)回調(diào)上來(lái)的
//而其他時(shí)候都是通過(guò)讀回調(diào)上來(lái)的
//ooxxoo
//在通信完成后斷開(kāi)設(shè)備連接
self.centralManager cancelPeripheralConnection:peripheral];
}
//通知回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
}