前一階段剛剛做完了對(duì)接體脂稱和智能音箱忌愚,空下來整理一下藍(lán)牙的簡(jiǎn)單集成,不瞎叭叭了,進(jìn)入正題。
一嬉荆、概況
藍(lán)牙4.0標(biāo)準(zhǔn)包括傳統(tǒng)藍(lán)牙部分和低功耗藍(lán)牙模塊部分
二、藍(lán)牙模塊
藍(lán)牙模塊是指集成藍(lán)牙功能的芯片基本電路集合简十,用于短距離2.4G的無線通訊模塊。
對(duì)于終端用戶來說撬腾,藍(lán)牙模塊是半成品螟蝙,通過在模塊的基礎(chǔ)上做功能二次開發(fā)、封裝外殼等工序时鸵,實(shí)現(xiàn)能夠利用藍(lán)牙通訊的最終產(chǎn)品胶逢。
藍(lán)牙模塊按照應(yīng)用和支持協(xié)議劃分主要分為兩種:
1厅瞎、經(jīng)典藍(lán)牙模塊(BT):
泛指支持藍(lán)牙協(xié)議在4.0以下的模塊,一般用于數(shù)量比較的傳輸初坠,如:語音和簸、音樂等較高數(shù)據(jù)量傳輸。
經(jīng)典藍(lán)牙模塊可再細(xì)分為傳統(tǒng)藍(lán)牙模塊和高速藍(lán)牙模塊
高速模塊相比于傳輸藍(lán)牙模塊速率提高到約24Mbps,是傳統(tǒng)藍(lán)牙模塊的八倍碟刺,可以用于錄像機(jī)锁保、高清電視、PC半沽、PMP爽柒、UMPC和打印機(jī)之間的資料傳輸。
2者填、低功耗藍(lán)牙模塊(BLE):
指支持藍(lán)牙協(xié)議4.0或更高的模塊浩村,也成為BLE模塊,最大的特點(diǎn)是成本和功效的降低占哟,應(yīng)用于實(shí)時(shí)性要去比較高的產(chǎn)品中心墅,比如:智能家居類(藍(lán)牙鎖、藍(lán)牙燈)榨乎、傳感設(shè)備的數(shù)據(jù)發(fā)送(血壓計(jì)怎燥、溫度傳感器)、消費(fèi)類電子(電子煙蜜暑、遙控玩具)等铐姚。
藍(lán)牙低功耗技術(shù)采用可變連接時(shí)間間隔,這個(gè)間隔根據(jù)具體應(yīng)用可以設(shè)置為幾毫秒到幾秒不等肛捍。
另外隐绵,因?yàn)锽LE技術(shù)采用非常快速的連接方式篇梭,因此可以處于“非連接”狀態(tài)(節(jié)省能源)氢橙,此時(shí)鏈路兩端只有在必要時(shí)才能開啟鏈路酝枢,然后在盡可能短的時(shí)間內(nèi)關(guān)閉鏈路恬偷。
三、藍(lán)牙模塊對(duì)比
1帘睦、按協(xié)議劃分
單模藍(lán)牙模塊:是指支持藍(lán)牙某一種協(xié)議的模塊
雙模藍(lán)牙模塊:是指同時(shí)支持經(jīng)典藍(lán)牙(BT)和低功耗藍(lán)牙(BLE) 協(xié)議的模塊
2袍患、按應(yīng)用分
藍(lán)牙數(shù)據(jù)模塊:一般多使用BLE地功耗藍(lán)牙模塊,擁有極低的運(yùn)行和待機(jī)功耗竣付,使用一顆紐扣電池可連續(xù)工作數(shù)年之久诡延。
藍(lán)牙音頻模塊:音頻需要大碼流的數(shù)據(jù)傳輸更適合使用BT經(jīng)典藍(lán)牙模塊。
3古胆、按照濕度等級(jí)分為:
工業(yè)級(jí):溫度范圍-40攝氏度~85攝氏度肆良,能夠在室外筛璧、干擾大等惡劣環(huán)境下正常運(yùn)行
商業(yè)級(jí):溫度范圍為0攝氏度~70攝氏度,一般應(yīng)用在普通民用產(chǎn)品中惹恃。
四夭谤、集成
iOS對(duì)藍(lán)牙庫進(jìn)行了封裝,封裝在CoreBluetooth庫
import CoreBluetooth
接下來是對(duì)一些代碼中需要使用到的名詞的備注
CBCentralManager - 中心管理者
CBPeripheralManager - 外設(shè)管理者
CBPeripheral - 外設(shè)對(duì)象
CBService - 外設(shè)服務(wù)
CBCharacteristic - 外設(shè)服務(wù)的特征
注:一個(gè)CBPeripheral可以包含多個(gè)CBService,而一個(gè)CBService也可以包含多個(gè)CBCharacteristic
接下來介紹藍(lán)牙從打開到連接到發(fā)送數(shù)據(jù)到接收數(shù)據(jù)的一整個(gè)流程
1巫糙、設(shè)置權(quán)限朗儒,在info.plist里面加入
Privacy - Bluetooth Peripheral Usage Description
2、開發(fā)流程
/******藍(lán)牙******/
var bleManager: CBCentralManager? //系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象
var peripheral: CBPeripheral? //外圍設(shè)備
var writeChar: CBCharacteristic? // 寫服務(wù)的特征值
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - 可以不用系統(tǒng)的藍(lán)牙提示框参淹,使用自定義的藍(lán)牙提示框
self.bleManager = CBCentralManager.init(delegate: self, queue: nil, options: nil)
//MARK:-使用系統(tǒng)的藍(lán)牙提示框
// self.bleManager = CBCentralManager.init(delegate: self, queue: nil)
// 設(shè)置代理
self.bleManager?.delegate = nil
}
- 判斷藍(lán)牙狀態(tài)
// 判斷藍(lán)牙狀態(tài)醉锄,通過CBCentralManager的state來獲取
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
// 確認(rèn)藍(lán)牙打開狀態(tài),然后進(jìn)行一系列的操作
// MARK: - 根據(jù)UUID篩選
// let uuid = CBUUID(string: "0000180A-0000-1000-8000-00805F9B34FB")
// self.bleManager?.scanForPeripherals(withServices: [uuid], options: nil)
// MARK: - 根據(jù)名字篩選
self.blueToothStatus = true
self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
GMLog("藍(lán)牙打開浙值,正在掃描")
case .unknown:
GMAlert.gm_alert(title: "", message: "藍(lán)牙權(quán)限不清楚", cancelBtnTitle: "設(shè)置", okBtnTitle: "好") { (alertIndex) in
}
case .poweredOff:
GMAlert.gm_alert(title: "", message: "打開藍(lán)牙來允許\"****\"連接到配件", cancelBtnTitle: "設(shè)置", okBtnTitle: "好") {[weak self] (alertIndex) in
if alertIndex == .ok {
GMLog("ok")
GMAlert.gm_alertSingle(title: "提示", message: "手機(jī)藍(lán)牙未開啟\n請(qǐng)?jiān)谠O(shè)置中開啟藍(lán)牙", okBtnTitle: "確定", handler: { (alertIndex) in
if #available(iOS 10.0, *) {
self?.settingAuthority()
} else {
// Fallback on earlier versions
}
})
}else {
GMLog("設(shè)置")
if #available(iOS 10.0, *) {
self?.settingAuthority()
} else {
// Fallback on earlier versions
GMLog("版本低于10")
}
}
}
default:
GMLog("----藍(lán)牙關(guān)閉")
}
}
- 掃描到外設(shè)
// 掃描到外設(shè)
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
GMLog("----scanPeripheral: \(peripheral)") // , \(peripheral.identifier.uuidString), \(peripheral.identifier.uuid)
GMLog("*********adver:\(advertisementData)")
// NSData *data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];
let data = advertisementData["kCBAdvDataManufacturerData"]
if data != nil {
let dataString = dataToString(data: data as! Data)
}
// 連接設(shè)備
self.bleManager?.connect(peripheral, options: nil)
}
連接成功&失敗
// 連接成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
GMLog("----連接成功")
// GMLog("\(self.peripheral)")
// 停止掃描
self.bleManager?.stopScan()
self.peripheral = peripheral
peripheral.delegate = self
self.peripheral?.delegate = self
peripheral.discoverServices(nil)
// let uuid = "9000"
// peripheral.discoverServices([CBUUID.init(string: uuid)])
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
GMLog("----連接失敗")
}
- 斷開設(shè)備連接
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
GMLog("----斷開連接, \(self.isBindState), \(String(describing: self.peripheral))")
self.peripheral = nil
self.writeChar = nil
// 如果需要重新掃描
// self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
}
- 搜索到服務(wù)
//一旦我們讀取到外設(shè)的相關(guān)服務(wù)UUID就會(huì)回調(diào)下面的方法
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for aService in peripheral.services! {
GMLog("aaaaa-----\(aService.uuid.uuidString)")
// let uuid = "9000"
peripheral.discoverCharacteristics(nil, for: aService)
// peripheral.discoverCharacteristics([CBUUID.init(string: uuid)], for: aService)
// 過濾條件
// if aService.uuid == CBUUID.init(string: "0xFFF0") {
// peripheral.discoverCharacteristics(nil, for: aService)
// }
}
}
- 搜索到特征
// 根據(jù)特征值去判斷讀操作還是寫操作
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
// GMLog("!!!!!!!!!!!!!!!!\(service.characteristics)")
for aChar in service.characteristics! {
GMLog("#########\(aChar.uuid.uuidString)")
self.writeChar = aChar
// self.sendValue1()
// 過濾條件
// if aChar.uuid == CBUUID.init(string: "0xFFF1") { // 0xFFF1 寫數(shù)據(jù)
//
// self.writeChar = aChar
// }
// // 0xFFF4 讀數(shù)據(jù)
// else if aChar.uuid == CBUUID.init(string: "0xFFF4") {
//
// peripheral.setNotifyValue(true, for: aChar)
// }
if aChar.uuid == CBUUID.init(string: "9999") {
peripheral.setNotifyValue(true, for: aChar)
}
}
}
- 收到外設(shè)消息更新
// 發(fā)送
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
self.sendValue1()
// if characteristic.isNotifying {
// self.sendValue1()
// }
}
// 接收
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else {
return
}
GMLog("\(characteristic)")
GMLog("----接收數(shù)據(jù):\(self.dataToString(data: data))")
let dataString = self.dataToString(data: data)
let resultString = String(dataString.suffix(dataString.count - 12))
// let acceptString = string(from: self.dataTo(from: resultString))
self.deviceId = string(from: self.dataTo(from: resultString))
GMLog("\(string(from: self.dataTo(from: resultString)))")
// didDisconnectPeripheral
// MARK: - 如果接收到deviceId 斷開藍(lán)牙恳不;否則繼續(xù)接收
if self.deviceId != nil {
if let peripheral = self.peripheral {
self.bleManager?.cancelPeripheralConnection(peripheral)
}
}
// didDisconnectPeripheral
// self.bleManager?.cancelPeripheralConnection(self.peripheral!)
// FIXME: - 接收到數(shù)據(jù)后請(qǐng)求后臺(tái)接口
self.newAnlanyData(data: data)
}
//向peripheral中寫入數(shù)據(jù)后的回調(diào)函數(shù)
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
GMLog("寫入成功")
}
/// 新的計(jì)算方法
private func newAnlanyData(data: Data) {
if data[0] == 0xCF {
GMLog("進(jìn)這里")
} else if data[0] == 0x00 && data.count == 2 {
self.sendDeviceOff()
}
}
}
說明
- 藍(lán)牙的流程:搜索-連接-連接成功/失敗(設(shè)置外設(shè)代理开呐,搜索服務(wù))-搜索到服務(wù)(搜索特征)-搜索到特征-監(jiān)聽需要的特征(讀寫妆够、讀、寫等根據(jù)情況來確定)-通過外設(shè)讀寫特征寫入指令-收到設(shè)備返回信息-斷開連接
- CBCentralManagerDelegate:中心管理者代理负蚊,負(fù)責(zé)搜索神妹,設(shè)備狀態(tài)的一些回調(diào)
CBPeripheralDelegate:外設(shè)代理,負(fù)責(zé)對(duì)外設(shè)的一些操作家妆,特征的訂閱鸵荠,以及設(shè)備信息和消息的更新回調(diào)
16進(jìn)制的轉(zhuǎn)換
16進(jìn)制類型的字符串[A-F,0-9]和Data之間的轉(zhuǎn)換可以使用下面的方法。如果是包含=之類的可以直接用字符串轉(zhuǎn)換Data即可
extension String {
///16進(jìn)制字符串轉(zhuǎn)Data
func hexData() -> Data? {
var data = Data(capacity: count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, flags, stop in
let byteString = (self as NSString).substring(with: match!.range)
var num = UInt8(byteString, radix: 16)!
data.append(&num, count: 1)
}
guard data.count > 0 else { return nil }
return data
}
func utf8Data()-> Data? {
return self.data(using: .utf8)
}
}
extension Data {
///Data轉(zhuǎn)16進(jìn)制字符串
func hexString() -> String {
return map { String(format: "%02x", $0) }.joined(separator: "").uppercased()
}
}
OTA升級(jí)
OTA是DFU(Device Firmware Update)的一種類型伤极,準(zhǔn)確說蛹找,OTA的全稱應(yīng)該是OTA DFU,就是設(shè)備固件升級(jí)的意思哨坪。只不過大家為了方便起見庸疾,直接用OTA來指代固件空中升級(jí)(有時(shí)候大家也將OTA稱為FOTA)。
OTA升級(jí)并不復(fù)雜当编,只需要按照硬件定制的協(xié)議届慈,把數(shù)據(jù)按照正常的寫入方式發(fā)送給硬件即可(注意查看硬件是否規(guī)定數(shù)據(jù)的大小端)
大端模式:是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中忿偷,這樣的存儲(chǔ)模式有點(diǎn)類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加金顿,而數(shù)據(jù)從高位往低位放;這和我們的閱讀習(xí)慣一致鲤桥。
小端模式:是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中揍拆,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中,這種存儲(chǔ)模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來茶凳,高地址部分權(quán)值高嫂拴,低地址部分權(quán)值低播揪。