藍(lán)牙屬于近場(chǎng)通訊中的一種宛畦,
iOS
中使用Core Bluetooth
框架實(shí)現(xiàn)藍(lán)牙通信,Core Bluetooth
支持藍(lán)牙低功耗的4.0
模式,就是通常說(shuō)稱(chēng)之的BLE
渗常,在生活中BLE
無(wú)處不在掠械,例如智能家居由缆,健身器材和智能玩具等,利用蘋(píng)果提供的Core Bluetooth
框架可以實(shí)現(xiàn)和BLE
設(shè)備進(jìn)行通信猾蒂。
藍(lán)牙中各角色的理解
在藍(lán)牙開(kāi)發(fā)中我們把提供服務(wù)的一方稱(chēng)之為周邊設(shè)備均唉,接收服務(wù)的一方稱(chēng)之為中央設(shè)備,典型的例子就是蘋(píng)果手表和iPhone
配對(duì)時(shí)的關(guān)系肚菠,蘋(píng)果手表向iPhone
提供用戶(hù)的運(yùn)動(dòng)數(shù)據(jù)舔箭,所以此種情況蘋(píng)果手表是周邊設(shè)備,iPhone
是中央設(shè)備,在Core Bluetooth
框架中分別對(duì)應(yīng)如下:
-
centralManager
:中央設(shè)備的處理類(lèi) -
peripheralManager
:周邊設(shè)備的處理類(lèi)
明確了周邊設(shè)備和中央設(shè)備后层扶,接下來(lái)是如何發(fā)現(xiàn)對(duì)方并建立連接箫章,在我們平時(shí)使用的手機(jī)搜索藍(lán)牙的過(guò)程中,都是先從搜索列表中選擇某個(gè)藍(lán)牙設(shè)備镜会,在進(jìn)行配對(duì)連接檬寂。peripheral
通過(guò)廣播的形式向外界提供service
,service
會(huì)綁定一個(gè)獨(dú)一無(wú)二的UUID
戳表,有BTSIG UUID
和Custom UUID
二種桶至,UUID
用來(lái)確定中央設(shè)備連接周邊設(shè)備時(shí)確定身份用的。
每個(gè)service
會(huì)有多個(gè)characteristic
匾旭,characteristic
也有自己的UUID
镣屹,characteristic
可以理解為周邊設(shè)備提供的具體服務(wù),其UUID
用來(lái)區(qū)分提供的每一個(gè)具體服務(wù)价涝,因?yàn)橐粋€(gè)service
是可以提供多種具體服務(wù)的女蜈,中央設(shè)備通過(guò)UUID
來(lái)讀寫(xiě)這些服務(wù)。
在雙方建立了連接后就要商議如何發(fā)送和接受數(shù)據(jù)了飒泻,數(shù)據(jù)傳輸協(xié)議部分我們不用細(xì)究鞭光,Core Bluetooth
都為我們處理好了,至于MTU最大最大傳輸單元現(xiàn)在是是271bytes
泞遗,數(shù)據(jù)超過(guò)了就會(huì)分段發(fā)送惰许。
實(shí)戰(zhàn)演示
CBPeripheralManager
新建一個(gè)PeripheralViewController
類(lèi)并繼承UIViewController
,定義成員變量peripheralManager
并初始化史辙,同時(shí)設(shè)置代理汹买,由于篇幅有限這里只貼出關(guān)鍵代碼:
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
Peripheral Manager delegate
代理必須實(shí)現(xiàn)的方法如下:
extension PeripheralViewController: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .poweredOn:
textCharacteristic = CBMutableCharacteristic(type: textCharacteristicUUID, properties: .notify, value: nil, permissions: .readable)
mapCharacteristic = CBMutableCharacteristic(type: mapCharacteristicUUID, properties: .writeWithoutResponse, value: nil, permissions: .writeable)
let service = CBMutableService(type: TextOrMapServiceUUID, primary: true)
service.characteristics = [textCharacteristic, mapCharacteristic]
peripheralManager.add(service)
default: return
}
}
當(dāng)藍(lán)牙服務(wù)可用時(shí),需要?jiǎng)?chuàng)建service
并關(guān)聯(lián)相應(yīng)的characteristic
聊倔,代碼中的UUID
都是定義的字符串常量晦毙。
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [TextOrMapServiceUUID]])
通過(guò)startAdvertising
方法來(lái)向外界發(fā)送廣播。
由于
iOS
的限制耙蔑,當(dāng)iOS
設(shè)備作為周邊設(shè)備向外廣播時(shí)是無(wú)法利用CBAdvertisementDataManufacturerDataKey
攜帶manufacturer data
的见妒。
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral,
didSubscribeTo characteristic: CBCharacteristic) {
guard characteristic == textCharacteristic else { return }
prepareDataAndSend()
}
func prepareDataAndSend() {
guard let data = textView.text.data(using: .utf8) else { return }
self.dataToSend = data
sendDataIndex = 0
sendData()
}
func sendData() {
if sendingEOM {
let didSend = peripheralManager.updateValue("EOM".data(using: .utf8)!, for: textCharacteristic, onSubscribedCentrals: nil)
if didSend {
sendingEOM = false
print("Sent: EOM")
}
return
}
let numberOfBytes = (dataToSend as NSData).length
guard sendDataIndex < numberOfBytes else { return }
var didSend = true
while didSend {
var amountToSend = numberOfBytes - sendDataIndex
if amountToSend > notifyMTU {
amountToSend = notifyMTU
}
let chunk = dataToSend.withUnsafeBytes{(body: UnsafePointer<UInt8>) in
return Data(
bytes: body + sendDataIndex,
count: amountToSend
)
}
didSend = peripheralManager.updateValue(chunk, for: textCharacteristic, onSubscribedCentrals: [])
if !didSend { return }
guard let stringFromData = String(data: chunk, encoding: .utf8) else { return }
print("Sent: \(stringFromData)")
sendDataIndex += amountToSend
if sendDataIndex >= dataToSend.count {
sendingEOM = true
let eomSent = peripheralManager.updateValue("EOM".data(using: .utf8)!, for: textCharacteristic, onSubscribedCentrals: nil)
if eomSent {
sendingEOM = false
print("Sent: EOM")
}
return
}
}
}
此回調(diào)會(huì)在中央設(shè)備訂閱了當(dāng)初廣播的characteristic
時(shí)調(diào)用,這里我們準(zhǔn)備發(fā)送數(shù)據(jù)甸陌,發(fā)送數(shù)據(jù)的過(guò)程中和中央設(shè)備需要約定一個(gè)標(biāo)識(shí)表明數(shù)據(jù)是否發(fā)送完畢须揣,這里采用了EOM
標(biāo)志作為結(jié)束位,采用二進(jìn)制流的形式進(jìn)行發(fā)送钱豁。
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
sendData()
}
此回調(diào)在CBPeripheralManager
準(zhǔn)備發(fā)送下一段數(shù)據(jù)時(shí)發(fā)送耻卡,這里一般用來(lái)實(shí)現(xiàn)保證分段數(shù)據(jù)按順序發(fā)送給中央設(shè)備。
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
guard let request = requests.first, request.characteristic == mapCharacteristic else {
peripheral.respond(to: requests.first!, withResult: .attributeNotFound)
return
}
map() { locationManager?.stopUpdatingLocation() }
peripheral.respond(to: request, withResult: .success)
}
fileprivate func map(completionHandler: () -> Void) {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.requestWhenInUseAuthorization()
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager?.startUpdatingLocation()
}
}
此回調(diào)在中央設(shè)備針對(duì)響應(yīng)的characteristic
發(fā)送數(shù)據(jù)給外圍設(shè)備時(shí)調(diào)用牲尺,這里我們模擬中央設(shè)備發(fā)送打開(kāi)地圖的指令給iPhone
卵酪。
CBCentralManager
新建一個(gè)CentralViewController
類(lèi)并繼承UIViewController
,定義成員變量centralManager
并初始化,同時(shí)設(shè)置代理溃卡,由于篇幅有限這里只貼出關(guān)鍵代碼:
centralManager = CBCentralManager(delegate: self, queue: nil)
Central Manager delegate
代理必須要實(shí)現(xiàn)的方法如下:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn: scan()
case .poweredOff, .resetting: cleanup()
default: return
}
}
func scan() {
centralManager.scanForPeripherals(withServices: [TextOrMapServiceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: true as Bool)])
}
func cleanup() {
guard discoveredPeripheral?.state != .disconnected,
let services = discoveredPeripheral?.services else {
centralManager.cancelPeripheralConnection(discoveredPeripheral!)
return
}
for service in services {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid.isEqual(textCharacteristicUUID) {
if characteristic.isNotifying {
discoveredPeripheral?.setNotifyValue(false, for: characteristic)
return
}
}
}
}
}
centralManager.cancelPeripheralConnection(discoveredPeripheral!)
}
藍(lán)牙可用時(shí)開(kāi)始掃描溢豆,通過(guò)UUID
掃描外圍設(shè)備廣播的服務(wù)。
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard RSSI_range.contains(RSSI.intValue) && discoveredPeripheral != peripheral else { return }
discoveredPeripheral = peripheral
centralManager.connect(peripheral, options: [:])
}
需要檢查RSSI
強(qiáng)度塑煎,只有藍(lán)牙信號(hào)強(qiáng)度在一定范圍內(nèi)才開(kāi)始嘗試進(jìn)行連接沫换。
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if let error = error { print(error.localizedDescription) }
cleanup()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
centralManager.stopScan()
data.removeAll()
peripheral.delegate = self
peripheral.discoverServices([TextOrMapServiceUUID])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if (peripheral == discoveredPeripheral) {
cleanup()
}
scan()
}
以上是關(guān)于連接的幾個(gè)回調(diào)函數(shù),連接成功后就停止掃描最铁,然后調(diào)用peripheral.discoverServices
方法讯赏,這會(huì)來(lái)到Peripheral Delegate
中的相應(yīng)代理方法。
Peripheral Delegate
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error {
print(error.localizedDescription)
cleanup()
return
}
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics([textCharacteristicUUID, mapCharacteristicUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print(error.localizedDescription)
cleanup()
return
}
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid == textCharacteristicUUID {
textCharacteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
} else if characteristic.uuid == mapCharacteristicUUID {
mapCharacteristic = characteristic
}
}
}
此回調(diào)用來(lái)發(fā)現(xiàn)services
冷尉,實(shí)際開(kāi)發(fā)中這里可能用列表展示發(fā)現(xiàn)的服務(wù)漱挎,讓用戶(hù)進(jìn)行相應(yīng)的選擇。
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
if characteristic == textCharacteristic {
guard let newData = characteristic.value else { return }
let stringFromData = String(data: newData, encoding: .utf8)
if stringFromData == "EOM" {
textView.text = String(data: data, encoding: .utf8)
data.removeAll()
} else {
data.append(newData)
}
}
}
此回調(diào)對(duì)應(yīng)peripheralManager.updateValue
這個(gè)方法雀哨,能拿到外圍設(shè)備發(fā)送過(guò)來(lái)的數(shù)據(jù)磕谅。
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error { print(error.localizedDescription) }
guard characteristic.uuid == textCharacteristicUUID else { return }
if characteristic.isNotifying {
print("Notification began on \(characteristic)")
} else {
print("Notification stopped on \(characteristic). Disconnecting...")
}
}
此回調(diào)處理外圍設(shè)備的characteristic
通知,比如下線(xiàn)或者離開(kāi)的情況雾棺,這里進(jìn)行簡(jiǎn)單的打印膊夹。
總結(jié)
對(duì)藍(lán)牙開(kāi)發(fā)中的外圍設(shè)備,中央設(shè)備捌浩,UUID
放刨,service
和characteristic
等基本概念進(jìn)行了簡(jiǎn)單介紹,并利用Core Bluetooth
框架進(jìn)行了簡(jiǎn)單的demo
演示尸饺,主要是需要理解幾個(gè)特定代理方法即可进统,同時(shí)由于iOS
的限制,iPhone
在作為外設(shè)時(shí)在廣播的時(shí)候是不能發(fā)送額外數(shù)據(jù)的浪听,這點(diǎn)需要注意螟碎。