1. 程式人生 > >Swift藍芽開發詳解及示例

Swift藍芽開發詳解及示例

藍芽使用

距離上次部落格更新已經過去了好幾個月 這段時間一直在忙公司專案的重構和整體UI重做 一直抽不出時間來對做的東西做一個歸納和整理 前幾天專案成功上線了 現在對這段時間專案重構中遇到的問題和使用的技術做一些簡單的整理 首先介紹專案中用的的佔比較重的東西 藍芽的使用及藍芽互動邏輯的優化

iOS 藍芽簡介

  1. 藍芽模式簡介
    藍芽開發分為兩種模式,中心模式(central),和外設模式(peripheral)。一般來講,我們需要在軟體內連線硬體,通過連線硬體給硬體傳送指令以完成一些動作的藍芽開發都是基於中心模式(central)模式的開發,也就是說我們開發的app是中心,我們要連線的硬體是外設。如果需要其他裝置連線手機藍芽,並對手機進行一些操作,那就是基於外設模式(peripheral)的開發。 本次我們主要介紹的就是中心模式的藍芽開發
  2. 裝置簡介
    • 中心裝置(CBCentralManager):iOS系統的手機等裝置
    • 外圍裝置(CBPeripheral):手環等第三方裝置
  3. 藍芽資料傳輸簡介
    將外圍裝置(車輛)的資料傳送給中心裝置(手機)時, 資料是經過兩層包裝的
    第一層是 Service(服務) , 可以是一個或多個, 比如車輛資料(服務)
    第二層是 Characteristic(特徵) , 他提供了更多關於Service(服務)的資料, 例如車輛資料(服務)中包含了兩個資料, 分別是里程資料和續航資料, 這兩個就是車輛資料(服務)的具體資料(特徵)
  4. 具體操作簡介
    讀(read) , 寫(write) , 訂閱(notify)

    我們的目的是讀取裝置中的資料(read) , 或者給裝置寫入一定的資料(write)。有時候我們還想裝置的資料變化的時候不需要我們手動去讀取這個值,需要裝置自動通知我們它的值變化了,值是多少。把值告訴app,這個時候就需要訂閱這個特徵了(notify)

具體使用步驟

  • 資料讀寫步驟
    • 建立中心裝置(CBCentralManager)
    • 中心裝置開始掃描(scanForPeripherals)
    • 掃描到外圍裝置之後, 自動呼叫中心裝置的代理方法(didDiscoverPeripheral)
    • 如果裝置過多, 可以將掃描到的外圍裝置新增到陣列
    • 開始連線, 從陣列中過濾出自己想要的裝置, 進行連線(connectPeripheral)
    • 連線上之後, 自動呼叫中心裝置的代理方法(didConnectPeripheral), 在代理中, 進行查詢外圍裝置的服務(peripheral.discoverServices)
    • 查詢到服務之後, 自動呼叫外圍裝置的代理(didDiscoverServices), 可通過UUID,查詢具體的服務,查詢服務(discoverCharacteristics)
    • 查詢到特徵之後, 自動呼叫外圍裝置的代理(didDiscoverCharacteristics), 通過UUID找到自己想要的特徵, 讀取特徵(readValueForCharacteristic)
    • 讀取到特徵之後, 自動呼叫外設的代理方法(didUpdateValueForCharacteristic),在這裡列印或者解析自己想要的特徵值.

程式碼拆解實現

//建立中心裝置(CBCentralManager)
import Foundation
import CoreBluetooth

//用於看傳送資料是否成功!
class LLBlueTooth:NSObject {
    //單例物件
    internal static let instance = LLBlueTooth()
    //中心物件
    var central : CBCentralManager?
    //中心掃描到的裝置都可以儲存起來,
    //掃描到新裝置後可以通過通知的方式傳送出去,連線裝置介面可以接收通知,實時重新整理裝置列表
    var deviceList: NSMutableArray?
    // 當前連線的裝置
    var peripheral:CBPeripheral!
    //傳送資料特徵(連線到裝置之後可以把需要用到的特徵儲存起來,方便使用)
    var sendCharacteristic:CBCharacteristic?
    override init() {
        super.init()
        self.central = CBCentralManager.init(delegate:self, queue:nil, options:[CBCentralManagerOptionShowPowerAlertKey:false])
        self.deviceList = NSMutableArray()
    }
	// MARK: 掃描裝置的方法
    func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?){
       self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
    }
	// MARK: 停止掃描
    func stopScan() {
        self.central?.stopScan()
    }
    // MARK: 寫資料
    func writeToPeripheral(_ data: Data) {
        peripheral.writeValue(data , for: sendCharacteristic!, type: CBCharacteristicWriteType.withResponse)
    }   
    // MARK: 連線某個裝置的方法
    /*
     *  裝置有幾個狀態
     @available(iOS 7.0, *)
     public enum CBPeripheralState : Int {
         case disconnected
         case connecting
         case connected
         @available(iOS 9.0, *)
         case disconnecting
     }
     */
    func requestConnectPeripheral(_ model:CBPeripheral) {
        if (model.state != CBPeripheralState.connected) {
            central?.connect(model , options: nil)
        }
    }
}
//MARK: -- 中心管理器的代理
extension LLBlueTooth : CBCentralManagerDelegate{
    // MARK: 檢查執行這個App的裝置是不是支援BLE。
    func centralManagerDidUpdateState(_ central: CBCentralManager){
        if #available(iOS 10.0, *) {
            switch central.state {
            case CBManagerState.poweredOn:
                print("藍芽開啟")
            case CBManagerState.unauthorized:
                print("沒有藍芽功能")
            case CBManagerState.poweredOff:
                print("藍芽關閉")
            default:
                print("未知狀態")
            }
        }
        // 手機藍芽狀態發生變化,可以傳送通知出去。提示使用者
    }
    // 開始掃描之後會掃描到藍芽裝置,掃描到之後走到這個代理方法
    // MARK: 中心管理器掃描到了裝置
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        //  因為iOS目前不提供藍芽裝置的UUID的獲取,所以在這裡通過藍芽名稱判斷是否是本公司的裝置
        guard peripheral.name != nil , peripheral.name!.contains("藍芽名稱") else {
            return
        }
    }
    // MARK: 連線外設成功,開始發現服務
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){
        // 設定代理
        peripheral.delegate = self
        // 開始發現服務
        peripheral.discoverServices(nil)
        // 儲存當前連線裝置
        self.peripheral = peripheral
        // 這裡可以發通知出去告訴裝置連線介面連線成功
    }
    // MARK: 連線外設失敗
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        // 這裡可以發通知出去告訴裝置連線介面連線失敗
           }
    // MARK: 連線丟失
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NotificationCenter.default.post(name: Notification.Name(rawValue: "DidDisConnectPeriphernalNotification"), object: nil, userInfo: ["deviceList": self.deviceList as AnyObject])
        // 這裡可以發通知出去告訴裝置連線介面連線丟失
    }
}
// 外設的代理
extension LLBlueTooth : CBPeripheralDelegate {
    //MARK: - 匹配對應服務UUID
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
        if error != nil {
            return
        }
        for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service )
        }
    }
    //MARK: - 服務下的特徵
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
        if (error != nil){
            return
        }
        for  characteristic in service.characteristics! {

            switch characteristic.uuid.description {

            case "具體特徵值":
                // 訂閱特徵值,訂閱成功後後續所有的值變化都會自動通知
                peripheral.setNotifyValue(true, for: characteristic)
            case "******":
                // 讀區特徵值,只能讀到一次
peripheral.readValue(for:characteristic)
            default:
                print("掃描到其他特徵")
            }
        }
    }
    //MARK: - 特徵的訂閱狀體發生變化
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?){
        guard error == nil  else {
            return
        }
    }
    // MARK: - 獲取外設發來的資料
    // 注意,所有的,不管是 read , notify 的特徵的值都是在這裡讀取
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)-> (){
        if(error != nil){
            return
        }
        switch characteristic.uuid.uuidString {
        case "***************":
            print("接收到了裝置的溫度特徵的值的變化")
        default:
            print("收到了其他資料特徵資料: \(characteristic.uuid.uuidString)")
        }
    }
    //MARK: - 檢測中心向外設寫資料是否成功
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if(error != nil){
            print("傳送資料失敗!error資訊:\(String(describing: error))")
        }
    }
}
//MARK: - 以上就是藍芽模組的具體程式碼  在這裡可以使用一些已有封裝的第三方藍芽框架  只要按步驟做就可以

其他相關優化

  • 安全相關
    因為藍芽的操作可能會涉及到裝置的安全性問題,所以在安全方面需要考量一下,在這裡我提供一些我在安全方面所做的具體措施
    • 在連線到藍芽裝置以後,先與藍芽裝置及服務端做一次三方的安全驗證,使用一些加密演算法,保證當前是自己的APP對自己裝置下發的藍芽指令
    • 在所有的藍芽操作指令中增加時間戳安全判斷,可以跟硬體工程師商討具體設定的安全超時時長,時間超過預留時間的,不做響應
    • 在一些涉及到裝置和財產安全的藍芽操作上,可以加一些與服務端的實時安全校驗,保證當前藍芽指令的安全性
  • 效能相關
    • 因為受藍芽訊號強度限制,藍芽操作的靈敏度和響應時間會存在一定問題,所以要求硬體工程師對藍芽裝置的型號強度進行定製
    • 在進行藍芽裝置搜尋連線時,iOS沒有自帶的超時設定,如果不手動停止的話,會不停地進行裝置搜尋,影響裝置效能,所以建議設定一個超時時長,進行一段時間的搜尋以後停止對裝置的搜尋,並提示使用者進行重試