藍牙使用
距離上次博客更新已經過去了好幾個月 這段時間一直在忙公司項目的重構和整體UI重做 一直抽不出時間來對做的東西做一個歸納和整理 前幾天項目成功上線了 現在對這段時間項目重構中遇到的問題和使用的技術做一些簡單的整理 首先介紹項目中用的的佔比較重的東西 藍牙的使用及藍牙交互邏輯的優化
iOS 藍牙簡介
- 藍牙模式簡介
藍牙開發分爲兩種模式,中心模式(central),和外設模式(peripheral)。一般來講,我們需要在軟件內連接硬件,通過連接硬件給硬件發送指令以完成一些動作的藍牙開發都是基於中心模式(central)模式的開發,也就是說我們開發的app是中心,我們要連接的硬件是外設。如果需要其他設備連接手機藍牙,並對手機進行一些操作,那就是基於外設模式(peripheral)的開發。 本次我們主要介紹的就是中心模式的藍牙開發 - 設備簡介
- 中心設備(CBCentralManager):iOS系統的手機等設備
- 外圍設備(CBPeripheral):手環等第三方設備
- 藍牙數據傳輸簡介
將外圍設備(車輛)的數據傳送給中心設備(手機)時, 數據是經過兩層包裝的
第一層是 Service(服務) , 可以是一個或多個, 比如車輛數據(服務)
第二層是 Characteristic(特徵) , 他提供了更多關於Service(服務)的數據, 例如車輛數據(服務)中包含了兩個數據, 分別是里程數據和續航數據, 這兩個就是車輛數據(服務)的具體數據(特徵) - 具體操作簡介
讀(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沒有自帶的超時設置,如果不手動停止的話,會不停地進行設備搜索,影響設備性能,所以建議設置一個超時時長,進行一段時間的搜索以後停止對設備的搜索,並提示用戶進行重試