TempTrack-iOS/TempTrack/Bluetooth/BluetoothClient.swift

293 lines
8.7 KiB
Swift
Raw Normal View History

2023-06-03 08:15:00 +02:00
import Foundation
import SwiftUI
final class BluetoothClient: ObservableObject {
private let updateInterval = 3.0
2023-06-11 21:57:07 +02:00
private let minimumOffsetToUpdateDeviceClock = 5.0
2023-06-03 08:15:00 +02:00
private let connection = DeviceManager()
2023-06-11 21:57:07 +02:00
private let storage: TemperatureStorage
2023-06-14 16:16:56 +02:00
var hasInfo: Bool {
deviceInfo != nil
}
var isConnected: Bool {
if case .configured = deviceState {
return true
}
return false
}
2023-06-03 08:15:00 +02:00
2023-06-11 21:57:07 +02:00
init(storage: TemperatureStorage, deviceInfo: DeviceInfo? = nil) {
self.storage = storage
2023-06-03 08:15:00 +02:00
self.deviceInfo = deviceInfo
2023-06-11 21:57:07 +02:00
connection.delegate = self
2023-06-03 08:15:00 +02:00
}
func connect() -> Bool {
connection.connect()
}
@Published
private(set) var deviceState: DeviceState = .disconnected {
didSet {
2023-06-14 16:16:56 +02:00
log.info("State: \(deviceState)")
2023-06-03 08:15:00 +02:00
if case .configured = deviceState {
startRegularUpdates()
} else {
endRegularUpdates()
}
}
}
@Published
private(set) var deviceInfo: DeviceInfo? {
didSet {
2023-06-08 14:57:40 +02:00
updateDeviceTimeIfNeeded()
2023-06-14 16:16:56 +02:00
// collectRecordedData()
2023-06-13 17:14:57 +02:00
if let deviceInfo, let runningTransfer {
runningTransfer.update(info: deviceInfo)
let next = runningTransfer.nextRequest()
addRequest(next)
}
}
}
2023-06-03 08:15:00 +02:00
private var openRequests: [BluetoothRequest] = []
private var runningRequest: BluetoothRequest?
private var runningTransfer: TemperatureDataTransfer?
2023-06-08 14:57:40 +02:00
// MARK: Regular updates
2023-06-03 08:15:00 +02:00
func updateDeviceInfo() {
guard case .configured = deviceState else {
return
}
addRequest(.getInfo)
2023-06-03 08:15:00 +02:00
}
private var dataUpdateTimer: Timer?
private func startRegularUpdates() {
guard dataUpdateTimer == nil else {
return
}
2023-06-14 16:16:56 +02:00
log.info("Starting updates")
2023-06-03 08:15:00 +02:00
dataUpdateTimer = Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.updateDeviceInfo()
}
dataUpdateTimer?.fire()
}
private func endRegularUpdates() {
guard let dataUpdateTimer else {
return
}
dataUpdateTimer.invalidate()
runningRequest = nil
self.dataUpdateTimer = nil
2023-06-14 16:16:56 +02:00
log.info("Ending updates")
2023-06-03 08:15:00 +02:00
}
2023-06-08 14:57:40 +02:00
// MARK: Requests
2023-06-03 08:15:00 +02:00
private func performNextRequest() {
guard runningRequest == nil else {
return
}
guard !openRequests.isEmpty else {
return
}
let next = openRequests.removeFirst()
guard connection.send(next.serialized) else {
2023-06-14 16:16:56 +02:00
log.warning("Failed to start request \(next)")
2023-06-03 08:15:00 +02:00
performNextRequest()
return
}
runningRequest = next
}
func addRequest(_ request: BluetoothRequest) {
2023-06-13 17:14:57 +02:00
defer {
performNextRequest()
}
let type = request.byte
if let runningRequest, runningRequest.byte == type {
2023-06-14 16:16:56 +02:00
log.info("Skipping duplicate request \(request)")
2023-06-13 17:14:57 +02:00
return
}
guard !openRequests.contains(where: { $0.byte == type }) else {
2023-06-14 16:16:56 +02:00
log.info("Skipping duplicate request \(request)")
2023-06-13 17:14:57 +02:00
return
}
2023-06-03 08:15:00 +02:00
openRequests.append(request)
}
2023-06-08 14:57:40 +02:00
// MARK: Device time
private func updateDeviceTimeIfNeeded() {
2023-06-11 21:57:07 +02:00
guard let deviceInfo else {
2023-06-08 14:57:40 +02:00
return
}
2023-06-11 21:57:07 +02:00
guard !deviceInfo.hasDeviceStartTimeSet || deviceInfo.clockOffset > minimumOffsetToUpdateDeviceClock else {
2023-06-08 14:57:40 +02:00
return
}
2023-06-11 21:57:07 +02:00
2023-06-08 14:57:40 +02:00
guard !openRequests.contains(where: { if case .setDeviceStartTime = $0 { return true }; return false }) else {
return
}
2023-06-11 21:57:07 +02:00
let time = deviceInfo.deviceStartTime.seconds
2023-06-08 14:57:40 +02:00
addRequest(.setDeviceStartTime(deviceStartTimeSeconds: time))
2023-06-14 16:16:56 +02:00
log.info("Setting device start time to \(time) s (\(Date().seconds) current)")
2023-06-08 14:57:40 +02:00
}
// MARK: Data transfer
2023-06-03 08:15:00 +02:00
2023-06-08 14:57:40 +02:00
@discardableResult
2023-06-03 08:15:00 +02:00
func collectRecordedData() -> Bool {
2023-06-08 14:57:40 +02:00
guard runningTransfer == nil else {
2023-06-14 16:16:56 +02:00
log.info("Transfer already running")
2023-06-08 14:57:40 +02:00
return false
}
guard !openRequests.contains(where: { if case .getRecordingData = $0 { return true }; return false }) else {
2023-06-14 16:16:56 +02:00
log.info("Transfer already scheduled")
2023-06-08 14:57:40 +02:00
return false
}
2023-06-03 08:15:00 +02:00
guard let info = deviceInfo else {
2023-06-14 16:16:56 +02:00
log.warning("No device info to start transfer")
2023-06-03 08:15:00 +02:00
return false
}
2023-06-08 14:57:40 +02:00
guard info.numberOfStoredMeasurements > 0 else {
return false
}
2023-06-03 08:15:00 +02:00
let transfer = TemperatureDataTransfer(info: info)
runningTransfer = transfer
let next = transfer.nextRequest()
2023-06-14 16:16:56 +02:00
log.info("Starting transfer")
2023-06-03 08:15:00 +02:00
addRequest(next)
return true
}
private func didReceive(data: Data, offset: Int, count: Int) {
guard let runningTransfer else {
2023-06-14 16:16:56 +02:00
log.warning("No running transfer to process device data")
2023-06-03 08:15:00 +02:00
return // TODO: Start new transfer?
}
runningTransfer.add(data: data, offset: offset, count: count)
let next = runningTransfer.nextRequest()
addRequest(next)
}
private func decode(info: Data) {
2023-06-08 14:57:40 +02:00
guard let newInfo = try? DeviceInfo(info: info) else {
2023-06-14 16:16:56 +02:00
log.error("Failed to decode device info")
2023-06-03 08:15:00 +02:00
return
}
self.deviceInfo = newInfo
}
}
extension BluetoothClient: DeviceManagerDelegate {
func deviceManager(didReceive data: Data) {
defer {
performNextRequest()
}
guard let runningRequest else {
2023-06-14 16:16:56 +02:00
log.warning("No request active, but \(data) received")
2023-06-03 08:15:00 +02:00
return
}
2023-06-08 14:57:40 +02:00
self.runningRequest = nil
2023-06-03 08:15:00 +02:00
guard data.count > 0 else {
2023-06-14 16:16:56 +02:00
log.error("No response data for request \(runningRequest)")
2023-06-03 08:15:00 +02:00
return
}
guard let type = BluetoothResponseType(rawValue: data[0]) else {
2023-06-14 16:16:56 +02:00
log.error("Unknown response \(data[0]) for request \(runningRequest)")
2023-06-03 08:15:00 +02:00
return
}
2023-06-08 14:57:40 +02:00
switch type {
case .success:
break
case .responseInProgress:
2023-06-14 16:16:56 +02:00
log.info("Device is busy for \(runningRequest)")
2023-06-08 14:57:40 +02:00
// Retry the request
addRequest(runningRequest)
return
2023-06-13 17:14:57 +02:00
case .invalidNumberOfBytesToDelete:
guard case .clearRecordingBuffer = runningRequest else {
// If clearing the recording buffer fails due to byte mismatch,
// then requesting new info will resolve the mismatch, and the transfer will be resumed
addRequest(.getInfo)
return
}
2023-06-14 16:16:56 +02:00
log.error("Request \(runningRequest) received non-matching responde about number of bytes to delete")
2023-06-13 17:14:57 +02:00
case .responseTooLarge:
guard case .getRecordingData = runningRequest else {
// If requesting bytes fails due to the response size,
// then requesting new info will update the response size, and the transfer will be resumed
addRequest(.getInfo)
return
}
2023-06-14 16:16:56 +02:00
log.error("Unexpectedly exceeded payload size for request \(runningRequest)")
2023-06-08 14:57:40 +02:00
default:
2023-06-14 16:16:56 +02:00
log.error("Unknown response \(data[0]) for request \(runningRequest)")
2023-06-03 08:15:00 +02:00
// If clearing the recording buffer fails due to byte mismatch,
// then requesting new info will resolve the mismatch, and the transfer will be resumed
2023-06-13 17:14:57 +02:00
2023-06-03 08:15:00 +02:00
addRequest(.getInfo)
return
2023-06-08 14:57:40 +02:00
2023-06-03 08:15:00 +02:00
}
let payload = data.dropFirst()
switch runningRequest {
case .getInfo:
decode(info: payload)
case .getRecordingData(let offset, let count):
didReceive(data: payload, offset: offset, count: count)
case .clearRecordingBuffer:
2023-06-11 21:57:07 +02:00
didClearDeviceStorage()
2023-06-08 14:57:40 +02:00
case .setDeviceStartTime:
2023-06-14 16:16:56 +02:00
log.info("Device time set")
2023-06-08 14:57:40 +02:00
break
2023-06-03 08:15:00 +02:00
}
}
2023-06-11 21:57:07 +02:00
private func didClearDeviceStorage() {
guard let runningTransfer else {
2023-06-14 16:16:56 +02:00
log.warning("No running transfer after clearing device storage")
2023-06-11 21:57:07 +02:00
return
}
runningTransfer.completeTransfer()
storage.add(runningTransfer.measurements)
self.runningTransfer = nil
updateDeviceTimeIfNeeded()
}
2023-06-03 08:15:00 +02:00
func deviceManager(didChangeState state: DeviceState) {
DispatchQueue.main.async {
self.deviceState = state
}
}
}