iOS藍牙開發(fā)

藍牙屬于近場通訊中的一種,iOS 中使用Core Bluetooth 框架實現(xiàn)藍牙通信, Core Bluetooth支持藍牙低功耗的4.0模式,就是通常說稱之的BLE,在生活中BLE無處不在,例如智能家居,健身器材和智能玩具等,利用蘋果提供的Core Bluetooth框架可以實現(xiàn)和BLE設備進行通信。

藍牙中各角色的理解

在藍牙開發(fā)中我們把提供服務的一方稱之為周邊設備,接收服務的一方稱之為中央設備,典型的例子就是蘋果手表和iPhone配對時的關系,蘋果手表向iPhone提供用戶的運動數(shù)據(jù),所以此種情況蘋果手表是周邊設備,iPhone是中央設備,在Core Bluetooth 框架中分別對應如下:

  • centralManager:中央設備的處理類
  • peripheralManager:周邊設備的處理類

明確了周邊設備和中央設備后,接下來是如何發(fā)現(xiàn)對方并建立連接,在我們平時使用的手機搜索藍牙的過程中,都是先從搜索列表中選擇某個藍牙設備,在進行配對連接。peripheral通過廣播的形式向外界提供service,service會綁定一個獨一無二的UUID,有BTSIG UUIDCustom UUID二種,UUID用來確定中央設備連接周邊設備時確定身份用的。

每個service會有多個characteristic,characteristic也有自己的UUID,characteristic可以理解為周邊設備提供的具體服務,其UUID用來區(qū)分提供的每一個具體服務,因為一個service是可以提供多種具體服務的,中央設備通過UUID來讀寫這些服務。

在雙方建立了連接后就要商議如何發(fā)送和接受數(shù)據(jù)了,數(shù)據(jù)傳輸協(xié)議部分我們不用細究,Core Bluetooth都為我們處理好了,至于MTU最大最大傳輸單元現(xiàn)在是是271bytes,數(shù)據(jù)超過了就會分段發(fā)送。

實戰(zhàn)演示

CBPeripheralManager

新建一個PeripheralViewController類并繼承UIViewController,定義成員變量peripheralManager并初始化,同時設置代理,由于篇幅有限這里只貼出關鍵代碼:

peripheralManager = CBPeripheralManager(delegate: self, queue: nil)

Peripheral Manager delegate

代理必須實現(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
    }
  }

當藍牙服務可用時,需要創(chuàng)建service并關聯(lián)相應的characteristic,代碼中的UUID都是定義的字符串常量。

peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [TextOrMapServiceUUID]])

通過startAdvertising方法來向外界發(fā)送廣播。

由于iOS的限制,當iOS設備作為周邊設備向外廣播時是無法利用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
      }
    }
  }

此回調會在中央設備訂閱了當初廣播的characteristic時調用,這里我們準備發(fā)送數(shù)據(jù),發(fā)送數(shù)據(jù)的過程中和中央設備需要約定一個標識表明數(shù)據(jù)是否發(fā)送完畢,這里采用了EOM標志作為結束位,采用二進制流的形式進行發(fā)送。

  func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
    sendData()
  }

此回調在CBPeripheralManager準備發(fā)送下一段數(shù)據(jù)時發(fā)送,這里一般用來實現(xiàn)保證分段數(shù)據(jù)按順序發(fā)送給中央設備。

  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()
    }
  }

此回調在中央設備針對響應的characteristic發(fā)送數(shù)據(jù)給外圍設備時調用,這里我們模擬中央設備發(fā)送打開地圖的指令給iPhone。

CBCentralManager

新建一個CentralViewController類并繼承UIViewController,定義成員變量centralManager并初始化,同時設置代理,由于篇幅有限這里只貼出關鍵代碼:

centralManager = CBCentralManager(delegate: self, queue: nil)

Central Manager delegate

代理必須要實現(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!)
  }

藍牙可用時開始掃描,通過UUID掃描外圍設備廣播的服務。

  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強度,只有藍牙信號強度在一定范圍內才開始嘗試進行連接。

 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()
  }

以上是關于連接的幾個回調函數(shù),連接成功后就停止掃描,然后調用peripheral.discoverServices方法,這會來到Peripheral Delegate中的相應代理方法。

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
      }
    }
  }

此回調用來發(fā)現(xiàn)services,實際開發(fā)中這里可能用列表展示發(fā)現(xiàn)的服務,讓用戶進行相應的選擇。

  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)
      }
    }
  }

此回調對應peripheralManager.updateValue這個方法,能拿到外圍設備發(fā)送過來的數(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...")
    }
  }

此回調處理外圍設備的characteristic通知,比如下線或者離開的情況,這里進行簡單的打印。

總結

對藍牙開發(fā)中的外圍設備,中央設備,UUID,servicecharacteristic等基本概念進行了簡單介紹,并利用Core Bluetooth 框架進行了簡單的demo演示,主要是需要理解幾個特定代理方法即可,同時由于iOS的限制,iPhone在作為外設時在廣播的時候是不能發(fā)送額外數(shù)據(jù)的,這點需要注意。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • iOS的藍牙數(shù)據(jù)接收以及發(fā)送 名詞:Central(中心設備)、Peripheral(外圍設備)、advertis...
    TianBai閱讀 22,728評論 54 303
  • 首先先明白幾個名詞:Central(中心設備)、Peripheral(外圍設備)、advertising(廣告)、...
    shuaikun閱讀 508評論 0 0
  • iOS 中使用 Core Bluetooth 框架實現(xiàn)藍牙通信。Core Bluetooth 是基于藍牙 4.0 ...
    歐大帥Allen閱讀 2,889評論 0 3
  • 相比于網絡請求,藍牙是一種低功耗(low-energy)的通信手段。目前iOS的開發(fā)都是針對CoreBluetoo...
    huxinwen閱讀 10,731評論 0 11
  • @[TOC](iOS 藍牙開發(fā) ) 1.藍牙簡介 藍牙模式簡介藍牙開發(fā)分為兩種模式,中心模式(central),和...
    孔雨露閱讀 2,462評論 1 2

友情鏈接更多精彩內容