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(心率、步数等)

最省电,响应快

需要设备支持

 

posted on   youhui  阅读(12)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示