Wie hat alles angefangen?
Vor ungefähr einem Jahr habe ich dieses Gerät gekauft, um die Herzfrequenz (im Folgenden: HR) während des Trainings zu überwachen. Der Sensor ist über Bluetooth perfekt mit dem Telefon und der Smartwatch verbunden. In der Regel erfordern Fitnessanwendungen, die diese Art von Daten analysieren, entweder ein Abonnement oder sind mit unnötig komplexen Analysten ausgestattet, die für mich als normalen Benutzer nicht sehr interessant sind. Daher hatte ich die Idee, eine eigene Anwendung zur Überwachung der Herzfrequenz während des Trainings für IOS auf Swift zu schreiben.
Ein bisschen Theorie zur Bluetooth LE-Technologie
Bluetooth Low Energy ist ein sehr beliebtes und weit verbreitetes Datenaustauschprotokoll, das wir überall verwenden und das von Tag zu Tag beliebter wird. Ich habe sogar einen Wasserkocher in der Küche, der über BLE ferngesteuert wird. Übrigens: Geringer Energieverbrauch, stark reduzierter Stromverbrauch im Gegensatz zu "nacktem" Bluetooth, so dass das Gerät mehrere Monate oder sogar Jahre lang mit diesem Protokoll auf einer Batterie kommunizieren kann.
Natürlich macht es keinen Sinn , die BLE 5.2- Protokollspezifikation zu zitieren und neu zu schreiben , daher beschränken wir uns auf grundlegende Konzepte.
Zentral- und Peripheriegerät
Je nach Verwendung und Zweck kann das Bluetooth-Gerät:
Zentral (Haupt) - empfängt Daten von einem Peripheriegerät (unserem Telefon)
- , ( )
, : , . , , , .
. , , , , , :
() - , . .
- . , .
, , - . 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)")
}
}
!
. :)
Im Allgemeinen war die Anleitung zur Verwendung von Bluetooth zum Anschließen eines Herzfrequenzsensors etwas umfangreich und manchmal schwierig. Ich hoffe, dass ich es geschafft habe, die Hauptbedeutung zu vermitteln. Natürlich gibt es einige weitere nicht implementierte Methoden, die hinzugefügt werden könnten (z. B. die Wiederverbindungsmethode, wenn die Verbindung unterbrochen wird), aber ich hielt diese Einstellung für ausreichend, um die Prägnanz und Bequemlichkeit der Bibliothek auf schnellem CoreBluetooth mäßig zu schätzen.
Alles Gute und vielen Dank!