Menghubungkan sensor detak jantung dada melalui Bluetooth ke Swift

Bagaimana semuanya dimulai?

Sekitar setahun yang lalu, saya membeli perangkat ini untuk memantau detak jantung (selanjutnya - HR) selama latihan. Sensor terhubung dengan sempurna ke ponsel, jam tangan pintar melalui Bluetooth, tetapi biasanya aplikasi kebugaran yang menganalisis data semacam ini memerlukan langganan atau dimuat dengan analis kompleks yang tidak perlu yang tidak terlalu menarik bagi saya sebagai pengguna biasa. Oleh karena itu, saya mendapat ide untuk menulis aplikasi saya sendiri untuk memantau detak jantung selama latihan untuk IOS di Swift.





Sedikit teori tentang teknologi Bluetooth LE

Bluetooth Low Energy adalah protokol pertukaran data yang sangat populer dan tersebar luas yang kami gunakan di mana-mana dan menjadi semakin populer setiap hari. Saya bahkan memiliki ketel di dapur yang dikendalikan dari jarak jauh melalui BLE. Energi rendah, omong-omong, jauh mengurangi konsumsi daya, berbeda dengan Bluetooth "telanjang", begitu berkurang sehingga perangkat siap untuk berkomunikasi menggunakan protokol ini pada satu baterai selama beberapa bulan, atau bahkan bertahun-tahun.





Tentu saja, tidak ada gunanya mengutip dan menulis ulang spesifikasi protokol BLE 5.2, jadi kami akan membatasi diri pada konsep dasar.





Perangkat pusat dan periferal

Bergantung pada penggunaan dan tujuan, perangkat Bluetooth dapat:





  • Pusat (utama) - menerima data dari perangkat periferal (telepon kami)





  • - , ( )





, : , . , , , .





. , , , , , :





  • () - , . .





  • - . , .





, , - . UUID, 16- 128-, .





Xcode , Label Main.storyboard outlets labels View Controller, constraints, viewDidLoad, :





outlets "121" "", view, .





, Bluetooth.





Info.plist : Bluetooth Always Usage Description , Bluetooth . , "" . !





Bluetooth

, :





import CoreBluetooth
      
      



, , , .





() :





var centralManager: CBCentralManager!
      
      



, ViewController , CBCentralManagerDelegate. extension ViewController, .





extension ViewController: CBCentralManagerDelegate {}
      
      



Xcode : "Type 'ViewController' does not conform to protocol 'CBCentralManagerDelegate'", , : "func centralManagerDidUpdateState(_ central: CBCentralManager)". "fix", . , .





, "func centralManagerDidUpdateState(_ central: CBCentralManager)" :





 func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        }
      
      



Xcode , . print(" "):





   extension ViewController: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print ("central.state is unknown")
        case .resetting:
            print ("central.state is resetting")
        case .unsupported:
            print ("central.state is unsupported")
        case .unauthorized:
            print ("central.state is unauthorized")
        case .poweredOff:
            print ("central.state is poweredOff")
        case .poweredOn:
            print ("central.state is poweredOn")
        @unknown default:
            break
        }
    }
}
      
      



"centralManager" . "viewDidLoad", "nil", Bluetooth .





override func viewDidLoad() {
        super.viewDidLoad()
        centralManager = CBCentralManager(delegate: self, queue: nil)
        heartRateLabel.isHidden = true
        bodyLocationLabel.isHidden = true
    }
      
      



, Bluetooth, , "central.state is poweredOn", , . Bluetooth , "central.state is poweredOff".





Bluetooth

, . "centralManagerDidUpdateState" ".poweredOn" "print" :





centralManager.scanForPeripherals(withServices: nil)
      
      



, , extension ViewController "centralManagerDidUpdateState" :





 func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print(peripheral)
    }
      
      



... . ! . , , .





UUID

Bluetooth , , UUID . UUID , : "0x180D". outlets:





let heartRateUUID = CBUUID(string: "0x180D")
      
      



"centralManager.scanForPeripherals(withServices: nil)" :





case .poweredOn:
            print ("central.state is poweredOn")
            centralManager.scanForPeripherals(withServices: [heartRateUUID] )
      
      



UUID, :





<CBPeripheral: 0x280214000, identifier = D5A5CD3E-33AC-7245-4294-4FFB9B986DFC, name = COOSPO H6 0062870, state = disconnected>





, , "var centralManager: CBCentralManager!" :





var heartRatePeripheral: CBPeripheral!
      
      



"didDiscover peripheral" :





 func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print(peripheral)
        heartRatePeripheral = peripheral
        centralManager.stopScan()
    }
      
      



"centralManager.stopScan()":





centralManager.connect(heartRatePeripheral, options: nil)
      
      



, , "didConnect peripheral" "didDiscover peripheral", :





func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print(" ")
    }
      
      



, " ". , .





, , (), . "heartRatePeripheral.discoverServices()" "didConnect", :





func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print(" ")
        heartRatePeripheral.discoverServices(nil)
    }
      
      



, , "CBPeripheralDelegate" "peripheral(_:didDiscoverServices:)" :





extension ViewController: CBPeripheralDelegate {
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            print(service)
        }
    }
}

      
      



, . , "heartRatePeripheral". :





  func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print(peripheral)
        heartRatePeripheral = peripheral
        
        heartRatePeripheral.delegate = self
        
        centralManager.stopScan()
        centralManager.connect(heartRatePeripheral, options: nil)
    }
      
      



, , , :





<CBService: 0x2824b4340, isPrimary = YES, UUID = Heart Rate>





<CBService: 0x2824b4240, isPrimary = YES, UUID = Battery>





<CBService: 0x2824b4280, isPrimary = YES, UUID = Device Information>





<CBService: 0x2824b4200, isPrimary = YES, UUID = 8FC3FD00-F21D-11E3-976C-0002A5D5C51B>





. UUID "heartRatePeripheral.discoverServices()"





heartRatePeripheral.discoverServices([heartRateUUID])
      
      



"<CBService: 0x2824b4340, isPrimary = YES, UUID = Heart Rate>", - (№ ).





- , , . , "didDiscoverServices - peripheral" - :





extension ViewController: CBPeripheralDelegate {
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
}
      
      



, "CBPeripheralDelegate" "didDiscoverCharacteristicsFor". :





func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            print(characteristic)
        }
    }
      
      



, , , :





<CBCharacteristic: 0x28024c120, UUID = 2A37, properties = 0x10, value = {length = 2, bytes = 0x0469}, notifying = NO>





<CBCharacteristic: 0x28024c180, UUID = 2A38, properties = 0x2, value = {length = 1, bytes = 0x01}, notifying = NO>





, , . Bluetooth , UUID = 2A37 , UUID = 2A38 . , .





:





 let heartRateUUID = CBUUID(string: "0x180D")
 let heartRateCharacteristicCBUUID = CBUUID(string: "2A37")
 let bodyLocationCharacteristicCBUUID = CBUUID(string: "2A38")
      
      



. , ".notify" .. , ".read", .. . , .





, . "peripheral.readValue(for: characteristic)"





 func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            peripheral.readValue(for: characteristic)
        }
    }
      
      



, , "peripheral(_:didUpdateValueFor:error:)" "CBPeripheralDelegate", , "switch - case", :





func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
                error: Error?) {
  switch characteristic.uuid {
    case bodySensorLocationCharacteristicCBUUID:
      print(characteristic.value ?? "no value")
    default:
      print("Unhandled Characteristic UUID: \(characteristic.uuid)")
  }
}
      
      



"1 bytes". , "data".





"" , , , . , :





      private func bodyLocation(from characteristic: CBCharacteristic) -> String {
        guard let characteristicData = characteristic.value,
              let byte = characteristicData.first else { return "Error" }
        switch byte {
        case 0: return ""
        case 1: return ""
        case 2: return ""
        case 3: return ""
        case 4: return ""
        case 5: return " "
        case 6: return ""
        default:
            return ""
        }
    }
      
      



"didUpdateValueFor characteristic", ( label ):





   func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
                    error: Error?) {
        
        switch characteristic.uuid {
        
        case bodyLocationCharacteristicCBUUID:
            let bodySensorLocation = bodyLocation(from: characteristic)
            bodyLocationLabel.text = bodySensorLocation
            bodyLocationLabel.isHidden = false
          
        default:
          print("Unhandled Characteristic UUID: \(characteristic.uuid)")
      }
        
    }
      
      



! , !





, , :)






, . , ".notify", " ", . "peripheral.setNotifyValue(true, for: characteristic)" "didDiscoverCharacteristicsFor service:





func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            peripheral.readValue(for: characteristic)
            peripheral.setNotifyValue(true, for: characteristic)
        }
    }
      
      



, :





Unhandled Characteristic UUID: 2A37





Unhandled Characteristic UUID: 2A37





Unhandled Characteristic UUID: 2A37





. , . 1 2 . , "" "CBPeripheralDelegate".





  private func heartRate(from characteristic: CBCharacteristic) -> Int {
        guard let characteristicData = characteristic.value else { return -1 }
        let byteArray = [UInt8](characteristicData)
        
        let firstBitValue = byteArray[0] & 0x01
        if firstBitValue == 0 {
            return Int(byteArray[1])
        } else {
            return (Int(byteArray[1]) << 8) + Int(byteArray[2])
        }
    }
      
      



, , case "peripheral(_:didUpdateValueFor:error:)", , label :





   func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
                    error: Error?) {
        
        switch characteristic.uuid {
        
        case bodyLocationCharacteristicCBUUID:
            let bodySensorLocation = bodyLocation(from: characteristic)
            bodyLocationLabel.text = bodySensorLocation
            bodyLocationLabel.isHidden = false
            
        case heartRateCharacteristicCBUUID:
            let bpm = heartRate(from: characteristic)
            heartRateLabel.text = String(bpm)
            heartRateLabel.isHidden = false
            
        default:
          print("Unhandled Characteristic UUID: \(characteristic.uuid)")
      }
    }
      
      



!





. :)






Secara umum panduan tentang cara menggunakan Bluetooth untuk menghubungkan sensor detak jantung keluar sedikit besar dan terkadang sulit, semoga saya berhasil menyampaikan makna utamanya. Tentu saja, ada beberapa metode yang tidak diimplementasikan yang dapat ditambahkan (misalnya, metode penyambungan kembali ketika koneksi terputus), tetapi saya menganggap set ini cukup untuk menghargai keringkasan dan kenyamanan pustaka di CoreBluetooth cepat.





Semua sukses dan terima kasih!








All Articles