看過一些藍牙App的事例,大體上對藍牙的連接過程進行了了解。但是開始真正自己寫一個小的BLE程序的時候就舉步維艱了。那些模棱兩可的概念在頭腦中瞬間就蒸發了,所以還是決定從最基本的藍牙連接過程進行。這里所說的藍牙是針對 bluetooth 4.0的。
第一步就是去看官方的關於藍牙框架的文檔,即Core Bluetooth Programming Guide,在蘋果的官方網站上可以輕松找到,不管你對藍牙的基本概念是否有了解,這個文件可以使你更好的對藍牙的連接過程有個了解。這個文檔的前面幾張介紹了關於bluetooth 4.0開發過程中必要的概念(這些概念必不可少,一定要搞懂,否則后面會搞得很亂),真正開始將連接過程是從Performing Common Central Role Tasks 這一章開始,這里很清晰的將過程分成了以下這么幾步,每一步對有對應的接口函數,只需要按着這一步一步寫下去就可以。
- Start up a central manager object
- Discover and connect to peripheral devices that are advertising
- Explore the data on a peripheral device after you’ve connected to it
- Send read and write requests to a characteristic value of a peripheral’s service
- Subscribe to a characteristic’s value to be notified when it is updated
針對每一步我將我的Swift所對應的代碼列出來:
Starting Up a Central Manager
將CBCenteralManager實例化,如下:
1 //start up a central manager object 2 func startCentralManager(){ 3 myCentralManager = CBCentralManager(delegate: self, queue: nil) 4 }
當實例化成功后,對調用如下函數:
1 func centralManagerDidUpdateState(central: CBCentralManager!){ 2 println("CentralManager is initialized") 3 4 switch central.state{ 5 case CBCentralManagerState.Unauthorized: 6 println("The app is not authorized to use Bluetooth low energy.") 7 case CBCentralManagerState.PoweredOff: 8 println("Bluetooth is currently powered off.") 9 case CBCentralManagerState.PoweredOn: 10 println("Bluetooth is currently powered on and available to use.") 11 default:break 12 } 13 }
此時CBCenteralManager實例化完畢,就可以開始進行掃描外設了。
2、Discovering Peripheral Devices That Are Advertising
OC中掃描函數是:
1 [myCentralManager scanForPeripheralsWithServices:nil options:nil];
在swift中對應的是:
1 myCentralManager!.scanForPeripheralsWithServices(nil , options: nil)
當發現設備后,會調用如下的函數,這樣我們就可以對設備進行一定的操作,當發現設備后接下來應該是連接設備。
1 func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) { 2 println("CenCentalManagerDelegate didDiscoverPeripheral") 3 println("Discovered \(peripheral.name)") 4 println("Rssi: \(RSSI)") 5 6 println("Stop scan the Ble Devices") 7 myCentralManager!.stopScan() 8 cbPeripheral = peripheral 9 10 }
3、Connecting to a Peripheral Device After You’ve Discovered It
OC中連接設備的代碼是:
1 [myCentralManager connectPeripheral:peripheral options:nil];
Swift中的代碼是:
1 myCentralManager!.connectPeripheral(cbPeripheral!, options: nil)
連接的結果會調用下面三個回調函數
1 func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) { 2 println("CenCentalManagerDelegate didConnectPeripheral") 3 println("Connected with \(peripheral.name)") 4 peripheral.delegate = self 5 6 peripheral.discoverServices(nil) 7 } 8 9 func centralManager(central: CBCentralManager!, didFailToConnectPeripheral peripheral: CBPeripheral!, error: NSError!) { 10 println("CenCentalManagerDelegate didFailToConnectPeripheral") 11 } 12 13 func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!) { 14 println("CenCentalManagerDelegate didDisconnectPeripheral") 15 }
具體含義可以查詢官方文檔。
注意:連接設備的函數不能在didDiscoverPeripheral回調函數中直接調用,這樣是無法連接的,這個問題也困擾了我一會,后面歪打正着就成了, 要么設一個延遲再去connect,要么加個按鈕觸發事件再去連接。
4、Discovering the Services of a Peripheral That You’re Connected To
當連接完畢后,就是掃描這個外設(Peripheral)所支持服務。然后可以保存下來,下次可以直接調用
OC中掃描Services的代碼如下:
1 [peripheral discoverServices:nil];
Swift中如下:
1 peripheral.discoverServices(nil)
當掃描到Service后對調用下面的回調函數:
1 func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) { 2 println("CBPeripheralDelegate didDiscoverServices") 3 for service in peripheral.services { 4 println("Discover service \(service)") 5 println("UUID \(service.UUID)") 6 if(service.UUID == CBUUID.UUIDWithString("1802")){ 7 println("Immediate_Alert_Service") 8 immediateAlertService = (service as CBService) 9 peripheral.discoverCharacteristics(nil , forService: immediateAlertService) 10 }else if(service.UUID == CBUUID.UUIDWithString("1803")){ 11 println("Link_Loss_Service") 12 linkLossAlertService = (service as CBService) 13 peripheral.discoverCharacteristics(nil , forService: linkLossAlertService) 14 } 15 } 16 }
掃描到Service后,要遍歷這個Service所包含的Characteristics。
5、Discovering the Characteristics of a Service
掃描Characteristics
1 peripheral.discoverCharacteristics(nil , forService: linkLossAlertService)
和掃描Services一樣,會有回調函數
1 func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) { 2 for characteristic in service.characteristics{ 3 4 if(service == immediateAlertService && characteristic.UUID == CBUUID.UUIDWithString("2A06")){ 5 println("immediateAlertService Discover characteristic \(characteristic)") 6 alertLevelCharacteristic = (characteristic as CBCharacteristic) 7 //immediateAlertCharacter 寫入是有問題的 8 // cbPeripheral!.writeValue(NSData(bytes: &alertLevel, length: 1), forCharacteristic: characteristic as CBCharacteristic, type: CBCharacteristicWriteType.WithResponse) 9 }else if(service == linkLossAlertService && characteristic.UUID == CBUUID.UUIDWithString("2A06")){ 10 println("linkLossAlertService Discover characteristic \(characteristic)") 11 linkLossAlertCharacteristic = (characteristic as CBCharacteristic) 12 //linkLossAlertCharacteristic 寫入沒有問題,所以通過這個寫入來進行綁定 13 cbPeripheral!.writeValue(NSData(bytes: &alertLevel, length: 1), forCharacteristic: characteristic as CBCharacteristic, type: CBCharacteristicWriteType.WithResponse) 14 } 15 } 16 }
這樣就把每個Service所對應的Characteristics給讀取出來了。
6、Retrieving the Value of a Characteristic
- Reading the Value of a Characteristic
1 cbPeripheral!.readValueForCharacteristic(alertLevelCharacteristic!)
值在回調函數中獲取,在read之前,要注意這個Characteristic是否可讀。
1 func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) { 2 if(error != nil){ 3 println("Error Reading characteristic value: \(error.localizedDescription)") 4 }else{ 5 var data = characteristic.value 6 println("Update value is \(data)") 7 } 8 9 }
- Writing the Value of a Characteristic
1 var alertLevel:Byte = 0x02 2 cbPeripheral!.writeValue(NSData(bytes: &alertLevel, length: 1), forCharacteristic: alertLevelCharacteristic!, type: CBCharacteristicWriteType.WithoutResponse)
寫是否成功會根據CBCharacteristicWriteType來決定是否調用下面的回調函數:
1 func peripheral(peripheral: CBPeripheral!, didWriteValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) { 2 if(error != nil){ 3 println("Error writing characteristic value: \(error.localizedDescription)") 4 }else{ 5 println("Write value success!") 6 } 7 8 }
關於write我這里還有些注意的地方要強調!!!!
並不是每一個Characteristic都可以通過回調函數來查看它寫入狀態的。就比如針對 immediateAlertService(1802) 的 alertLevelCharacteristic(2A06),就是一個不能有response的Characteristic。剛開始我就一直用CBCharacteristicWriteType.WithResponse來進行寫入始終不成功,郁悶壞了,最后看到每個Characteristic還有個屬性值是指示這個的,我將每個Characteristic打印出來有如下信息:
immediateAlertService Discover characteristic <CBCharacteristic: 0x15574d00, UUID = 2A06, properties = 0x4, value = (null), notifying = NO> linkLossAlertService Discover characteristic <CBCharacteristic: 0x15671d00, UUID = 2A06, properties = 0xA, value = (null), notifying = NO>
這個的properties是什么剛開始不知道,覺得他沒意義,后面才注意到properties是Characteristic的一個參數,具體解釋如下:
Declaration SWIFT struct CBCharacteristicProperties : RawOptionSetType { init(_ value: UInt) var value: UInt static var Broadcast: CBCharacteristicProperties { get } static var Read: CBCharacteristicProperties { get } static var WriteWithoutResponse: CBCharacteristicProperties { get } static var Write: CBCharacteristicProperties { get } static var Notify: CBCharacteristicProperties { get } static var Indicate: CBCharacteristicProperties { get } static var AuthenticatedSignedWrites: CBCharacteristicProperties { get } static var ExtendedProperties: CBCharacteristicProperties { get } static var NotifyEncryptionRequired: CBCharacteristicProperties { get } static var IndicateEncryptionRequired: CBCharacteristicProperties { get } } OBJECTIVE-C typedef enum { CBCharacteristicPropertyBroadcast = 0x01, CBCharacteristicPropertyRead = 0x02, CBCharacteristicPropertyWriteWithoutResponse = 0x04, CBCharacteristicPropertyWrite = 0x08, CBCharacteristicPropertyNotify = 0x10, CBCharacteristicPropertyIndicate = 0x20, CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40, CBCharacteristicPropertyExtendedProperties = 0x80, CBCharacteristicPropertyNotifyEncryptionRequired = 0x100, CBCharacteristicPropertyIndicateEncryptionRequired = 0x200, } CBCharacteristicProperties;
可以看到0x04對應的是CBCharacteristicPropertyWriteWithoutResponse
0x0A對應的是CBCharacteristicPropertyNotify
所以 immediateAlertService(1802) 的 alertLevelCharacteristic(2A06)是不能用CBCharacteristicWriteType.WithRespons進行寫入,只能用CBCharacteristicWriteType.WithOutRespons。這樣在以后的開發中可以對每個Characteristic的這個參數進行檢查再進行設置。
最后講一下關於藍牙綁定的過程,在iOS中,沒有講當綁定的過程,直接就是掃描、連接、交互。從而很多人會認為,連接就是綁定了,其實不然。在iOS開發中,連接並沒有完成綁定,在網上找到了個很好的解釋:
you cannot initiate pairing from the iOS central side. Instead, you have to read/write a characteristic value,and then let your peripheral respond with an "Insufficient Authentication" error.iOS will then initiate pairing, will store the keys for later use (bonding) and encrypts the link. As far as I know,it also caches discovery information, so that future connections can be set up faster.
就是當發生讀寫交互時,系統在會和外設進行綁定操作!!!