Skip to content

Commit

Permalink
BluetoothCadenceSensor周りを修正した
Browse files Browse the repository at this point in the history
  • Loading branch information
Gurrium committed Jun 6, 2022
1 parent d241c3b commit a1cb665
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 119 deletions.
122 changes: 8 additions & 114 deletions Komusou/BluetoothCadenceSensor.swift
Expand Up @@ -5,122 +5,16 @@
// Created by gurrium on 2022/03/17.
//

import Combine
import CoreBluetooth
import Foundation

final class BluetoothCadenceSensor: NSObject, CadenceSensor {
// CadenceSensor
var delegate: CadenceSensorDelegate?
final class BluetoothCadenceSensor: CadenceSensor {
private(set) var cadence: Published<Int?>.Publisher!
@Published
private var _cadence: Int?

// TODO: スピードでも使うので一元管理する
// TODO: 複数のCBCentralManagerを作ってもいいならこのまま
private let centralManager = CBCentralManager()

private var isBluetoothEnabled = false {
didSet {
if isBluetoothEnabled {
centralManager.scanForPeripherals(withServices: [.cyclingSpeedAndCadence], options: nil)
}
}
}

private var connectedPeripheral: CBPeripheral?
// speed measurement
private var cadence: Double = 0 {
didSet {
delegate?.onCadenceUpdate(cadence)
}
}

private var previousCrankEventTime: UInt16?
private var previousCumulativeCrankRevolutions: UInt16?
private var cadenceMeasurementPauseCounter = 0 {
didSet {
if cadenceMeasurementPauseCounter > 2 {
cadence = 0
}
}
}

override init() {
super.init()

centralManager.delegate = self
}
}

extension BluetoothCadenceSensor: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
isBluetoothEnabled = central.state == .poweredOn
}

func centralManager(_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData _: [String: Any], rssi _: NSNumber) {
// ここで参照を保持しないと破棄される
connectedPeripheral = peripheral

centralManager.connect(peripheral, options: nil)
}

func centralManager(_: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([.cyclingSpeedAndCadence])
}
}

extension BluetoothCadenceSensor: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
guard let service = peripheral.services?.first(where: { $0.uuid == .cyclingSpeedAndCadence }) else { return }

peripheral.discoverCharacteristics([.cscMeasurement], for: service)
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
guard let characteristic = service.characteristics?.first(where: { $0.uuid == .cscMeasurement }),
characteristic.properties.contains(.notify) else { return }

peripheral.setNotifyValue(true, for: characteristic)
}

func peripheral(_: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error _: Error?) {
guard let data = characteristic.value else { return }

let value = [UInt8](data)
guard (value[0] & 0b0010) > 0 else { return }

// ref: https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-5/
if let retrieved = parseCadence(from: value) {
cadenceMeasurementPauseCounter = 0

cadence = retrieved
} else {
cadenceMeasurementPauseCounter += 1
}
}

private func parseCadence(from value: [UInt8]) -> Double? {
precondition(value[0] & 0b0010 > 0, "Crank Revolution Data Present Flag is not set")

let cumulativeCrankRevolutions = (UInt16(value[2]) << 8) + UInt16(value[1])
let crankEventTime = (UInt16(value[4]) << 8) + UInt16(value[3])

defer {
previousCumulativeCrankRevolutions = cumulativeCrankRevolutions
previousCrankEventTime = crankEventTime
}

guard let previousCumulativeCrankRevolutions = previousCumulativeCrankRevolutions,
let previousCrankEventTime = previousCrankEventTime else { return nil }

let duration: UInt16

if previousCrankEventTime > crankEventTime {
duration = UInt16((UInt32(crankEventTime) + UInt32(UInt16.max) + 1) - UInt32(previousCrankEventTime))
} else {
duration = crankEventTime - previousCrankEventTime
}

guard duration > 0 else { return nil }

return (Double(cumulativeCrankRevolutions - previousCumulativeCrankRevolutions) * 60) / (Double(duration) / 1024)
init() {
cadence = $_cadence
BluetoothManager.shared().$cadence.assign(to: &$_cadence)
}
}
99 changes: 96 additions & 3 deletions Komusou/BluetoothManager.swift
Expand Up @@ -81,7 +81,9 @@ final class BluetoothManager: NSObject {
typealias ConnectingWithPeripheralFuture = Future<Void, ConnectingWithPeripheralError>

private static var sharedBluetoothManager: BluetoothManager!
// TODO: enum UserDefaultsKeyを作るとよさそう
private static let kSavedSpeedSensorUUIDKey = "speed_sensor_uuid_key"
private static let kSavedCadenceSensorUUIDKey = "cadence_sensor_uuid_key"

static func shared() -> BluetoothManager {
precondition(sharedBluetoothManager != nil, "Must call setUp(centralManager:) before use")
Expand Down Expand Up @@ -120,6 +122,26 @@ final class BluetoothManager: NSObject {
private var connectingSpeedSensorUUID: UUID?
private var speedSensorPromise: ConnectingWithPeripheralFuture.Promise?

// MARK: Cadence

@Published
private(set) var cadence: Int?
@Published
private(set) var connectedCadenceSensor: Peripheral?
private var previousCrankEventTime: UInt16?
private var previousCumulativeCrankRevolutions: UInt16?
private var cadenceMeasurementPauseCounter = 0 {
didSet {
if cadenceMeasurementPauseCounter > 2 {
cadence = 0
}
}
}
@AppStorage(kSavedCadenceSensorUUIDKey)
private var savedCadenceSensorUUID: UUID?
private var connectingCadenceSensorUUID: UUID?
private var cadenceSensorPromise: ConnectingWithPeripheralFuture.Promise?

private let centralManager: CentralManager
private var cancellables = Set<AnyCancellable>()
private var scannedSensors = [UUID: Peripheral]() {
Expand All @@ -142,20 +164,29 @@ final class BluetoothManager: NSObject {
{
connectToSpeedSensor(speedSensor)
}
// TODO: ケイデンスセンサー
if let savedCadenceSensorUUID = savedCadenceSensorUUID,
let cadenceSensor = self.centralManager.retrievePeripherals(withIdentifiers: [savedCadenceSensorUUID]).first
{
connectToCadenceSensor(cadenceSensor)
}

$connectedSpeedSensor.sink { [unowned self] sensor in
self.savedSpeedSensorUUID = sensor?.identifier
}
.store(in: &cancellables)
$connectedCadenceSensor.sink { [unowned self] sensor in
self.savedCadenceSensorUUID = sensor?.identifier
}
.store(in: &cancellables)
}

deinit {
// TODO:
// ケイデンスセンサーもやる
if let connectedSpeedSensor = connectedSpeedSensor {
centralManager.cancelPeripheralConnection(connectedSpeedSensor)
}
if let connectedCadenceSensor = connectedCadenceSensor {
centralManager.cancelPeripheralConnection(connectedCadenceSensor)
}

stopScan()
}
Expand Down Expand Up @@ -192,6 +223,27 @@ final class BluetoothManager: NSObject {
connectingSpeedSensorUUID = speedSensor.identifier
centralManager.connect(speedSensor, options: nil)
}

func connectToCadenceSensor(uuid: UUID) -> ConnectingWithPeripheralFuture {
guard let peripheral = scannedSensors[uuid] else {
return .init { $0(.failure(.init())) }
}

return .init { [weak self] promise in
self?.cadenceSensorPromise = promise

self?.connectToCadenceSensor(peripheral)
}
}

private func connectToCadenceSensor(_ cadenceSensor: Peripheral) {
if let cadenceSensor = connectedCadenceSensor {
centralManager.cancelPeripheralConnection(cadenceSensor)
}

connectingCadenceSensorUUID = cadenceSensor.identifier
centralManager.connect(cadenceSensor, options: nil)
}
}

extension BluetoothManager: CentralManagerDelegate {
Expand Down Expand Up @@ -220,6 +272,13 @@ extension BluetoothManager: CentralManagerDelegate {
connectedSpeedSensor = peripheral
speedSensorPromise?(.success(()))

peripheral.delegate = self
peripheral.discoverServices([.cyclingSpeedAndCadence])
case connectingCadenceSensorUUID:
connectingCadenceSensorUUID = nil
connectedCadenceSensor = peripheral
cadenceSensorPromise?(.success(()))

peripheral.delegate = self
peripheral.discoverServices([.cyclingSpeedAndCadence])
default:
Expand All @@ -236,6 +295,9 @@ extension BluetoothManager: CentralManagerDelegate {
case connectingSpeedSensorUUID:
connectingSpeedSensorUUID = nil
speedSensorPromise?(.failure(.init()))
case connectingCadenceSensorUUID:
connectingCadenceSensorUUID = nil
cadenceSensorPromise?(.failure(.init()))
default:
break
}
Expand Down Expand Up @@ -279,6 +341,10 @@ extension BluetoothManager: PeripheralDelegate {
speedMeasurementPauseCounter = 0

speed = retrieved
} else if let retrieved = parseCadence(from: value) {
cadenceMeasurementPauseCounter = 0

cadence = retrieved
} else {
speedMeasurementPauseCounter += 1
}
Expand Down Expand Up @@ -314,4 +380,31 @@ extension BluetoothManager: PeripheralDelegate {

return revolutionsPerSec * Double(tireSize.circumference) * 3600 / 1_000_000 // [km/h]
}

private func parseCadence(from value: [UInt8]) -> Int? {
precondition(value[0] & 0b0010 > 0, "Crank Revolution Data Present Flag is not set")

let cumulativeCrankRevolutions = (UInt16(value[2]) << 8) + UInt16(value[1])
let crankEventTime = (UInt16(value[4]) << 8) + UInt16(value[3])

defer {
previousCumulativeCrankRevolutions = cumulativeCrankRevolutions
previousCrankEventTime = crankEventTime
}

guard let previousCumulativeCrankRevolutions = previousCumulativeCrankRevolutions,
let previousCrankEventTime = previousCrankEventTime else { return nil }

let duration: UInt16

if previousCrankEventTime > crankEventTime {
duration = UInt16((UInt32(crankEventTime) + UInt32(UInt16.max) + 1) - UInt32(previousCrankEventTime))
} else {
duration = crankEventTime - previousCrankEventTime
}

guard duration > 0 else { return nil }

return Int((Double(cumulativeCrankRevolutions - previousCumulativeCrankRevolutions) * 60) / (Double(duration) / 1024))
}
}
3 changes: 1 addition & 2 deletions Komusou/BluetoothSpeedSensor.swift
Expand Up @@ -7,10 +7,9 @@

import Combine
import CoreBluetooth
import Foundation

final class BluetoothSpeedSensor: SpeedSensor {
var speed: Published<Double?>.Publisher!
private(set) var speed: Published<Double?>.Publisher!
@Published
private var _speed: Double?

Expand Down

0 comments on commit a1cb665

Please sign in to comment.