2023-06-03 08:15:00 +02:00
|
|
|
import Foundation
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
final class BluetoothClient: ObservableObject {
|
|
|
|
|
2023-06-08 09:52:20 +02:00
|
|
|
weak var delegate: TemperatureDataTransferDelegate?
|
|
|
|
|
2023-06-03 08:15:00 +02:00
|
|
|
private let updateInterval = 3.0
|
|
|
|
|
|
|
|
private let connection = DeviceManager()
|
|
|
|
|
|
|
|
init(deviceInfo: DeviceInfo? = nil) {
|
|
|
|
connection.delegate = self
|
|
|
|
self.deviceInfo = deviceInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func connect() -> Bool {
|
|
|
|
connection.connect()
|
|
|
|
}
|
|
|
|
|
|
|
|
@Published
|
|
|
|
private(set) var deviceState: DeviceState = .disconnected {
|
|
|
|
didSet {
|
|
|
|
print("State: \(deviceState.text)")
|
|
|
|
if case .configured = deviceState {
|
|
|
|
startRegularUpdates()
|
|
|
|
} else {
|
|
|
|
endRegularUpdates()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Published
|
2023-06-08 09:52:20 +02:00
|
|
|
private(set) var deviceInfo: DeviceInfo? {
|
|
|
|
didSet {
|
2023-06-08 14:57:40 +02:00
|
|
|
updateDeviceTimeIfNeeded()
|
|
|
|
collectRecordedData()
|
2023-06-08 09:52:20 +02:00
|
|
|
}
|
|
|
|
}
|
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() {
|
2023-06-08 09:52:20 +02:00
|
|
|
guard case .configured = deviceState else {
|
|
|
|
return
|
2023-06-05 13:05:57 +02:00
|
|
|
}
|
2023-06-08 09:52:20 +02:00
|
|
|
addRequest(.getInfo)
|
|
|
|
|
2023-06-03 08:15:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private var dataUpdateTimer: Timer?
|
|
|
|
|
|
|
|
private func startRegularUpdates() {
|
|
|
|
guard dataUpdateTimer == nil else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
print("Starting updates")
|
|
|
|
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
|
|
|
|
print("Ending updates")
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
//print("Starting request \(next)")
|
|
|
|
|
|
|
|
guard connection.send(next.serialized) else {
|
|
|
|
print("Failed to start request \(next)")
|
|
|
|
performNextRequest()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
runningRequest = next
|
|
|
|
}
|
|
|
|
|
|
|
|
func addRequest(_ request: BluetoothRequest) {
|
2023-06-08 14:57:40 +02:00
|
|
|
// TODO: Check if request already exists
|
2023-06-03 08:15:00 +02:00
|
|
|
openRequests.append(request)
|
|
|
|
performNextRequest()
|
|
|
|
}
|
|
|
|
|
2023-06-08 14:57:40 +02:00
|
|
|
// MARK: Device time
|
|
|
|
|
|
|
|
private func updateDeviceTimeIfNeeded() {
|
|
|
|
guard let info = deviceInfo else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard !info.hasDeviceStartTimeSet else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard !openRequests.contains(where: { if case .setDeviceStartTime = $0 { return true }; return false }) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let time = info.deviceStartTime.seconds
|
|
|
|
addRequest(.setDeviceStartTime(deviceStartTimeSeconds: time))
|
|
|
|
print("Setting device start time to \(time) s (\(Date().seconds) current)")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
guard !openRequests.contains(where: { if case .getRecordingData = $0 { return true }; return false }) else {
|
|
|
|
return false
|
|
|
|
}
|
2023-06-03 08:15:00 +02:00
|
|
|
guard let info = deviceInfo else {
|
|
|
|
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
|
2023-06-08 09:52:20 +02:00
|
|
|
runningTransfer?.delegate = delegate
|
2023-06-03 08:15:00 +02:00
|
|
|
let next = transfer.nextRequest()
|
|
|
|
addRequest(next)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
private func didReceive(data: Data, offset: Int, count: Int) {
|
|
|
|
guard let runningTransfer else {
|
|
|
|
return // TODO: Start new transfer?
|
|
|
|
}
|
|
|
|
runningTransfer.add(data: data, offset: offset, count: count)
|
|
|
|
continueTransfer()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func continueTransfer() {
|
|
|
|
guard let runningTransfer else {
|
|
|
|
return // TODO: Start new transfer?
|
|
|
|
}
|
|
|
|
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-03 08:15:00 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.deviceInfo = newInfo
|
|
|
|
guard let runningTransfer else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
runningTransfer.update(info: newInfo)
|
|
|
|
let next = runningTransfer.nextRequest()
|
|
|
|
addRequest(next)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension BluetoothClient: DeviceManagerDelegate {
|
|
|
|
|
|
|
|
func deviceManager(didReceive data: Data) {
|
|
|
|
defer {
|
|
|
|
performNextRequest()
|
|
|
|
}
|
|
|
|
guard let runningRequest else {
|
|
|
|
print("No request active, but \(data) received")
|
|
|
|
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 {
|
|
|
|
print("No response data for request \(runningRequest)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let type = BluetoothResponseType(rawValue: data[0]) else {
|
|
|
|
print("Unknown response \(data[0]) for request \(runningRequest)")
|
|
|
|
return
|
|
|
|
}
|
2023-06-08 14:57:40 +02:00
|
|
|
switch type {
|
|
|
|
case .success:
|
|
|
|
break
|
|
|
|
case .responseInProgress:
|
|
|
|
// Retry the request
|
|
|
|
addRequest(runningRequest)
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
print("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
|
|
|
|
// 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-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:
|
|
|
|
runningTransfer?.completeTransfer()
|
|
|
|
runningTransfer = nil
|
2023-06-08 14:57:40 +02:00
|
|
|
case .setDeviceStartTime:
|
|
|
|
print("Device time set")
|
|
|
|
break
|
2023-06-03 08:15:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func deviceManager(didChangeState state: DeviceState) {
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.deviceState = state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|