前一阶段刚刚做完了对接体脂称和智能音箱,空下来整理一下蓝牙的简单集成,不瞎叭叭了,进入正题。
一、概况
蓝牙4.0标准包括传统蓝牙部分和低功耗蓝牙模块部分
二、蓝牙模块
蓝牙模块是指集成蓝牙功能的芯片基本电路集合,用于短距离2.4G的无线通讯模块。
对于终端用户来说,蓝牙模块是半成品,通过在模块的基础上做功能二次开发、封装外壳等工序,实现能够利用蓝牙通讯的最终产品。
蓝牙模块按照应用和支持协议划分主要分为两种:
1、经典蓝牙模块(BT):
泛指支持蓝牙协议在4.0以下的模块,一般用于数量比较的传输,如:语音、音乐等较高数据量传输。
经典蓝牙模块可再细分为传统蓝牙模块和高速蓝牙模块
高速模块相比于传输蓝牙模块速率提高到约24Mbps,是传统蓝牙模块的八倍,可以用于录像机、高清电视、PC、PMP、UMPC和打印机之间的资料传输。
2、低功耗蓝牙模块(BLE):
指支持蓝牙协议4.0或更高的模块,也成为BLE模块,最大的特点是成本和功效的降低,应用于实时性要去比较高的产品中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。
蓝牙低功耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。
另外,因为BLE技术采用非常快速的连接方式,因此可以处于“非连接”状态(节省能源),此时链路两端只有在必要时才能开启链路,然后在尽可能短的时间内关闭链路。
三、蓝牙模块对比
1、按协议划分
单模蓝牙模块:是指支持蓝牙某一种协议的模块
双模蓝牙模块:是指同时支持经典蓝牙(BT)和低功耗蓝牙(BLE) 协议的模块
2、按应用分
蓝牙数据模块:一般多使用BLE地功耗蓝牙模块,拥有极低的运行和待机功耗,使用一颗纽扣电池可连续工作数年之久。
蓝牙音频模块:音频需要大码流的数据传输更适合使用BT经典蓝牙模块。
3、按照湿度等级分为:
工业级:温度范围-40摄氏度~85摄氏度,能够在室外、干扰大等恶劣环境下正常运行
商业级:温度范围为0摄氏度~70摄氏度,一般应用在普通民用产品中。
四、集成
iOS对蓝牙库进行了封装,封装在CoreBluetooth库
import CoreBluetooth
接下来是对一些代码中需要使用到的名词的备注
CBCentralManager - 中心管理者
CBPeripheralManager - 外设管理者
CBPeripheral - 外设对象
CBService - 外设服务
CBCharacteristic - 外设服务的特征
注:一个CBPeripheral可以包含多个CBService,而一个CBService也可以包含多个CBCharacteristic
接下来介绍蓝牙从打开到连接到发送数据到接收数据的一整个流程
1、设置权限,在info.plist里面加入
Privacy - Bluetooth Peripheral Usage Description
2、开发流程
/******蓝牙******/
var bleManager: CBCentralManager? //系统蓝牙设备管理对象
var peripheral: CBPeripheral? //外围设备
var writeChar: CBCharacteristic? // 写服务的特征值
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - 可以不用系统的蓝牙提示框,使用自定义的蓝牙提示框
self.bleManager = CBCentralManager.init(delegate: self, queue: nil, options: nil)
//MARK:-使用系统的蓝牙提示框
// self.bleManager = CBCentralManager.init(delegate: self, queue: nil)
// 设置代理
self.bleManager?.delegate = nil
}
- 判断蓝牙状态
// 判断蓝牙状态,通过CBCentralManager的state来获取
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
// 确认蓝牙打开状态,然后进行一系列的操作
// MARK: - 根据UUID筛选
// let uuid = CBUUID(string: "0000180A-0000-1000-8000-00805F9B34FB")
// self.bleManager?.scanForPeripherals(withServices: [uuid], options: nil)
// MARK: - 根据名字筛选
self.blueToothStatus = true
self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
GMLog("蓝牙打开,正在扫描")
case .unknown:
GMAlert.gm_alert(title: "", message: "蓝牙权限不清楚", cancelBtnTitle: "设置", okBtnTitle: "好") { (alertIndex) in
}
case .poweredOff:
GMAlert.gm_alert(title: "", message: "打开蓝牙来允许\"****\"连接到配件", cancelBtnTitle: "设置", okBtnTitle: "好") {[weak self] (alertIndex) in
if alertIndex == .ok {
GMLog("ok")
GMAlert.gm_alertSingle(title: "提示", message: "手机蓝牙未开启\n请在设置中开启蓝牙", okBtnTitle: "确定", handler: { (alertIndex) in
if #available(iOS 10.0, *) {
self?.settingAuthority()
} else {
// Fallback on earlier versions
}
})
}else {
GMLog("设置")
if #available(iOS 10.0, *) {
self?.settingAuthority()
} else {
// Fallback on earlier versions
GMLog("版本低于10")
}
}
}
default:
GMLog("----蓝牙关闭")
}
}
- 扫描到外设
// 扫描到外设
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)
}
// 连接设备
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("----连接失败")
}
- 断开设备连接
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)
}
- 搜索到服务
//一旦我们读取到外设的相关服务UUID就会回调下面的方法
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)
// }
}
}
- 搜索到特征
// 根据特征值去判断读操作还是写操作
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 写数据
//
// self.writeChar = aChar
// }
// // 0xFFF4 读数据
// 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)
}
}
}
- 收到外设消息更新
// 发送
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("----接收数据:\(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 断开蓝牙;否则继续接收
if self.deviceId != nil {
if let peripheral = self.peripheral {
self.bleManager?.cancelPeripheralConnection(peripheral)
}
}
// didDisconnectPeripheral
// self.bleManager?.cancelPeripheralConnection(self.peripheral!)
// FIXME: - 接收到数据后请求后台接口
self.newAnlanyData(data: data)
}
//向peripheral中写入数据后的回调函数
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
GMLog("写入成功")
}
/// 新的计算方法
private func newAnlanyData(data: Data) {
if data[0] == 0xCF {
GMLog("进这里")
} else if data[0] == 0x00 && data.count == 2 {
self.sendDeviceOff()
}
}
}
说明
- 蓝牙的流程:搜索-连接-连接成功/失败(设置外设代理,搜索服务)-搜索到服务(搜索特征)-搜索到特征-监听需要的特征(读写、读、写等根据情况来确定)-通过外设读写特征写入指令-收到设备返回信息-断开连接
- CBCentralManagerDelegate:中心管理者代理,负责搜索,设备状态的一些回调
CBPeripheralDelegate:外设代理,负责对外设的一些操作,特征的订阅,以及设备信息和消息的更新回调
16进制的转换
16进制类型的字符串[A-F,0-9]和Data之间的转换可以使用下面的方法。如果是包含=之类的可以直接用字符串转换Data即可
extension String {
///16进制字符串转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转16进制字符串
func hexString() -> String {
return map { String(format: "%02x", $0) }.joined(separator: "").uppercased()
}
}
OTA升级
OTA是DFU(Device Firmware Update)的一种类型,准确说,OTA的全称应该是OTA DFU,就是设备固件升级的意思。只不过大家为了方便起见,直接用OTA来指代固件空中升级(有时候大家也将OTA称为FOTA)。
OTA升级并不复杂,只需要按照硬件定制的协议,把数据按照正常的写入方式发送给硬件即可(注意查看硬件是否规定数据的大小端)
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。