それはどのように始まったのですか?
約1年前、トレーニング中の心拍数(以下、HR)を監視するためにこのデバイスを購入しました。センサーはBluetooth経由で電話やスマートウォッチに完全に接続しますが、通常、この種のデータを分析するフィットネスアプリケーションには、サブスクリプションが必要であるか、通常のユーザーとしてはあまり興味のない不必要に複雑なアナリストがロードされます。そのため、SwiftでのIOSのトレーニング中に心拍数を監視するための独自のアプリケーションを作成することを考えました。
BluetoothLEテクノロジーに関する少しの理論
Bluetooth Low Energyは非常に人気があり、広く普及しているデータ交換プロトコルであり、私たちがどこでも使用しており、毎日ますます人気が高まっています。私は、BLEを介してリモートで制御されるやかんをキッチンに持っています。ちなみに、低エネルギーは、「ベア」Bluetoothとは対照的に、消費電力を大幅に削減し、デバイスが1つのバッテリーでこのプロトコルを使用して数か月または数年も通信できるようになりました。
もちろん、BLE 5.2プロトコル仕様を引用して書き直す意味はないので、基本的な概念に限定します。
中央および周辺機器
用途と目的に応じて、Bluetoothデバイスは次のようになります。
中央(メイン)-周辺機器(電話)からデータを受信します
- , ( )
, : , . , , , .
. , , , , , :
() - , . .
- . , .
, , - . 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)")
}
}
!
. :)
一般的に、Bluetoothを使用して心拍数センサーを接続するためのガイドは少し大きく、時には難しいものでしたが、私は主な意味を伝えることができたと思います。もちろん、追加できる未実装のメソッドがいくつかあります(たとえば、接続が切断されたときの再接続メソッド)が、このセットは、高速CoreBluetoothのライブラリの簡潔さと利便性を適度に評価するのに十分であると考えました。
すべての成功と感謝!