iOS CoreBluetooth 框架
1. 发现 心率服务(0x180D)和 电池服务(0x180F)。
2. 读取 电池电量特征值(0x2A19)。
3.读取和监听 心率数据特征值(0x2A37),让设备主动通知 App 心率变化。
1. 扫描 BLE 设备,只搜索支持 心率服务 (0x180D) 的设备。
2. 连接设备 后,发现 心率服务 和 电池服务 (0x180F)。
3.读取电池电量 (0x2A19),电池电量是 0-100 的整数。
4. 监听心率 (0x2A37):
• 解析 8-bit 或 16-bit 的心率数据。
•设备每秒更新心率,主动通知 App。
5.解析 BLE 数据,并显示电池电量和心率信息。
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var heartRatePeripheral: CBPeripheral?
// 心率和电池服务 & 特征值 UUID
let heartRateServiceUUID = CBUUID(string: "180D") // 心率服务
let heartRateCharacteristicUUID = CBUUID(string: "2A37") // 心率数据特征值
let batteryServiceUUID = CBUUID(string: "180F") // 电池服务
let batteryCharacteristicUUID = CBUUID(string: "2A19") // 电池电量特征值
override func viewDidLoad() {
super.viewDidLoad()
// 初始化 Bluetooth 中心管理器
centralManager = CBCentralManager(delegate: self, queue: nil)
}
// MARK: - CBCentralManagerDelegate
// 1. 检查蓝牙状态
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("蓝牙状态未知")
case .resetting:
print("蓝牙正在重置")
case .unsupported:
print("设备不支持蓝牙")
case .unauthorized:
print("未授权使用蓝牙")
case .poweredOff:
print("蓝牙已关闭")
case .poweredOn:
print("蓝牙已开启,开始扫描设备...")
centralManager.scanForPeripherals(withServices: [heartRateServiceUUID], options: nil)
@unknown default:
print("未知蓝牙状态")
}
}
// 2. 发现 BLE 设备
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("发现设备: \(peripheral.name ?? "未知设备")")
heartRatePeripheral = peripheral
heartRatePeripheral?.delegate = self
centralManager.stopScan() // 停止扫描,连接设备
centralManager.connect(peripheral, options: nil)
}
// 3. 连接设备成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("成功连接设备: \(peripheral.name ?? "未知设备")")
peripheral.discoverServices([heartRateServiceUUID, batteryServiceUUID]) // 发现服务
}
// MARK: - CBPeripheralDelegate
// 4. 发现服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
print("发现服务: \(service.uuid)")
if service.uuid == heartRateServiceUUID || service.uuid == batteryServiceUUID {
peripheral.discoverCharacteristics(nil, for: service) // 发现特征值
}
}
}
// 5. 发现特征值
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print("发现特征值: \(characteristic.uuid)")
if characteristic.uuid == batteryCharacteristicUUID {
// 读取电池电量
peripheral.readValue(for: characteristic)
} else if characteristic.uuid == heartRateCharacteristicUUID {
// 监听心率数据(开启通知)
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
// 6. 读取数据(适用于电池电量)
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else { return }
if characteristic.uuid == batteryCharacteristicUUID {
let batteryLevel = data.first ?? 0
print("电池电量: \(batteryLevel)%")
} else if characteristic.uuid == heartRateCharacteristicUUID {
let heartRate = parseHeartRateData(data)
print("心率: \(heartRate) BPM")
}
}
// 解析心率数据(部分设备用第一个字节标志心率格式)
private func parseHeartRateData(_ data: Data) -> Int {
let bytes = [UInt8](data)
if bytes.first! & 0x01 == 0 {
return Int(bytes[1]) // 8-bit 心率值
} else {
return Int(bytes[1]) | (Int(bytes[2]) << 8) // 16-bit 心率值
}
}
// 7. 设备主动通知心率变化
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("设置通知失败: \(error!.localizedDescription)")
} else {
print("成功开启心率通知")
}
}
}
蓝牙已开启,开始扫描设备...
发现设备: MyHeartRateMonitor
成功连接设备: MyHeartRateMonitor
发现服务: 180D
发现服务: 180F
发现特征值: 2A37
发现特征值: 2A19
电池电量: 85%
成功开启心率通知
心率: 72 BPM
心率: 73 BPM
心率: 74 BPM
...
如果你的 BLE 设备 需要 定期主动获取(轮询)某个特征值(比如每隔 5 秒读取一次温度、血氧等),可以使用 定时器(Timer) 来定期读取特征值。
方法 1:使用 Timer 定时读取
可以使用 Timer.scheduledTimer 每隔 N 秒主动读取 BLE 设备的特征值:
查看代码
var readTimer: Timer? // 定时器
var myPeripheral: CBPeripheral? // 目标 BLE 设备
let myCharacteristicUUID = CBUUID(string: "2A19") // 例如读取电池电量(0x2A19)
// 启动定时器,每 5 秒读取一次特征值
func startReadingCharacteristic() {
readTimer?.invalidate() // 先清除旧定时器
readTimer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(readCharacteristicValue), userInfo: nil, repeats: true)
}
// 定时器触发,读取特征值
@objc func readCharacteristicValue() {
guard let peripheral = myPeripheral,
let characteristic = findCharacteristic(by: myCharacteristicUUID) else { return }
print("定时读取特征值: \(characteristic.uuid)")
peripheral.readValue(for: characteristic)
}
// 停止定时器
func stopReadingCharacteristic() {
readTimer?.invalidate()
readTimer = nil
}
// 通过 UUID 查找特征值
func findCharacteristic(by uuid: CBUUID) -> CBCharacteristic? {
guard let services = myPeripheral?.services else { return nil }
for service in services {
if let characteristic = service.characteristics?.first(where: { $0.uuid == uuid }) {
return characteristic
}
}
return nil
}
// 处理读取的特征值数据
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == myCharacteristicUUID, let data = characteristic.value {
let batteryLevel = data.first ?? 0
print("电池电量: \(batteryLevel)%")
}
}
✅ 特点:
•适用于 BLE 设备不支持 Notify,但你需要定期获取数据。
• 适用于 读取温度、血氧、电池等信息。
方法 2:使用 DispatchSource 定时读取
如果你的 BLE 读取任务运行在 后台线程,可以使用 DispatchSource 定时读取:
查看代码
var readTimer: DispatchSourceTimer?
func startReadingCharacteristicWithGCD() {
readTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
readTimer?.schedule(deadline: .now(), repeating: 5.0)
readTimer?.setEventHandler {
DispatchQueue.main.async {
self.readCharacteristicValue()
}
}
readTimer?.resume()
}
func stopReadingCharacteristicWithGCD() {
readTimer?.cancel()
readTimer = nil
}
✅ 特点:
• 不需要手动定时读取,BLE 设备主动通知更省电。
• 适用于心率、步数等动态数据。
适用场景 |
优点 |
缺点 |
|
方法 1: Timer 定时读取 |
设备不支持 Notify,需要手动读取 |
代码简单,适用于周期性数据 |
可能增加功耗 |
方法 2: DispatchSource 低功耗定时读取
|
后台任务,低功耗需求 |
更省电,适合后台读取 |
代码稍复杂 |
方法 3: Notify 监听设备主动推送
|
设备支持 Notify(心率、步数等) |
最省电,响应快 |
需要设备支持 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示