Connexion d'un capteur de fréquence cardiaque du torse via Bluetooth à Swift

Comment tout cela a-t-il commencé?

Il y a environ un an, j'ai acheté cet appareil pour surveiller la fréquence cardiaque (ci-après - FC) pendant l'entraînement. Le capteur se connecte parfaitement au téléphone, à la montre intelligente via Bluetooth, mais généralement les applications de fitness qui analysent ce type de données nécessitent soit un abonnement, soit sont chargées d'analystes inutilement complexes qui ne sont pas très intéressants pour moi en tant qu'utilisateur ordinaire. Par conséquent, j'ai eu l'idée d'écrire ma propre application pour surveiller la fréquence cardiaque lors des entraînements pour IOS sur Swift.





Un peu de théorie sur la technologie Bluetooth LE

Bluetooth Low Energy est un protocole d'échange de données très populaire et répandu que nous utilisons partout et qui devient de plus en plus populaire chaque jour. J'ai même une bouilloire dans la cuisine qui est contrôlée à distance via BLE. Faible consommation d'énergie, d'ailleurs, consommation d'énergie très réduite, contrairement au Bluetooth «nu», tellement réduit que l'appareil est prêt à communiquer en utilisant ce protocole sur une seule batterie pendant plusieurs mois, voire des années.





Bien sûr, il est inutile de citer et de réécrire la spécification du protocole BLE 5.2, nous nous limiterons donc aux concepts de base.





Appareil central et périphérique

Selon l'utilisation et le but, l'appareil Bluetooth peut être:





  • Central (principal) - reçoit les données d'un périphérique (notre téléphone)





  • - , ( )





, : , . , , , .





. , , , , , :





  • () - , . .





  • - . , .





, , - . 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)")
      }
    }
      
      



!





. :)






En général, le guide sur l'utilisation du Bluetooth pour connecter un capteur de fréquence cardiaque est sorti un peu gros et parfois difficile, j'espère avoir réussi à en transmettre le sens principal. Bien sûr, il existe quelques méthodes non implémentées supplémentaires qui pourraient être ajoutées (par exemple, la méthode de reconnexion lorsque la connexion est interrompue), mais j'ai considéré cet ensemble suffisant pour apprécier modérément la concision et la commodité de la bibliothèque sur Swift CoreBluetooth.





Tout le succès et merci!








All Articles