diff --git a/TempTrack.xcodeproj/project.pbxproj b/TempTrack.xcodeproj/project.pbxproj index d4cd673..068de4f 100644 --- a/TempTrack.xcodeproj/project.pbxproj +++ b/TempTrack.xcodeproj/project.pbxproj @@ -28,10 +28,8 @@ 88CDE0632A253AD900114294 /* TemperatureDataTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0622A253AD900114294 /* TemperatureDataTransfer.swift */; }; 88CDE0662A25D08F00114294 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 88CDE0652A25D08F00114294 /* SFSafeSymbols */; }; 88CDE0682A2698B400114294 /* TemperatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0672A2698B400114294 /* TemperatureStorage.swift */; }; - 88CDE06B2A2899C900114294 /* BottomSheet in Frameworks */ = {isa = PBXBuildFile; productRef = 88CDE06A2A2899C900114294 /* BottomSheet */; }; 88CDE06D2A28A92000114294 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE06C2A28A92000114294 /* DeviceInfo.swift */; }; 88CDE0702A28AEA300114294 /* TemperatureMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */; }; - 88CDE0722A28AEB900114294 /* TemperatureDataTransferDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */; }; 88CDE0742A28AEE500114294 /* DeviceManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */; }; 88CDE0762A28AF0900114294 /* TemperatureValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0752A28AF0900114294 /* TemperatureValue.swift */; }; 88CDE0782A28AF2C00114294 /* TemperatureSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */; }; @@ -39,7 +37,6 @@ 88CDE07E2A28AFF400114294 /* DeviceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE07D2A28AFF400114294 /* DeviceInfoView.swift */; }; E253A9222A2B39B700EC6B28 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E253A9212A2B39B700EC6B28 /* Color+Extensions.swift */; }; E253A9242A2B462500EC6B28 /* TemperatureHistoryChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = E253A9232A2B462500EC6B28 /* TemperatureHistoryChart.swift */; }; - E253A9272A2CA48A00EC6B28 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = E253A9262A2CA48A00EC6B28 /* SQLite */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -65,7 +62,6 @@ 88CDE0672A2698B400114294 /* TemperatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureStorage.swift; sourceTree = ""; }; 88CDE06C2A28A92000114294 /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureMeasurement.swift; sourceTree = ""; }; - 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureDataTransferDelegate.swift; sourceTree = ""; }; 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagerDelegate.swift; sourceTree = ""; }; 88CDE0752A28AF0900114294 /* TemperatureValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureValue.swift; sourceTree = ""; }; 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureSensor.swift; sourceTree = ""; }; @@ -81,9 +77,7 @@ buildActionMask = 2147483647; files = ( 88404DD02A2E718B00D30244 /* BinaryCodable in Frameworks */, - E253A9272A2CA48A00EC6B28 /* SQLite in Frameworks */, 88CDE0662A25D08F00114294 /* SFSafeSymbols in Frameworks */, - 88CDE06B2A2899C900114294 /* BottomSheet in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -143,7 +137,6 @@ isa = PBXGroup; children = ( 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */, - 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */, 88CDE0622A253AD900114294 /* TemperatureDataTransfer.swift */, 88CDE0752A28AF0900114294 /* TemperatureValue.swift */, 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */, @@ -208,8 +201,6 @@ name = TempTrack; packageProductDependencies = ( 88CDE0652A25D08F00114294 /* SFSafeSymbols */, - 88CDE06A2A2899C900114294 /* BottomSheet */, - E253A9262A2CA48A00EC6B28 /* SQLite */, 88404DCF2A2E718B00D30244 /* BinaryCodable */, ); productName = TempTrack; @@ -242,8 +233,6 @@ mainGroup = 88CDE0422A2508E800114294; packageReferences = ( 88CDE0642A25D08F00114294 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, - 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */, - E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */, 88404DCE2A2E718B00D30244 /* XCRemoteSwiftPackageReference "BinaryCodable" */, ); productRefGroup = 88CDE04C2A2508E900114294 /* Products */; @@ -281,7 +270,6 @@ 88CDE05D2A250F3C00114294 /* DeviceManager.swift in Sources */, 88404DDF2A2F68E100D30244 /* TemperatureDayOverview.swift in Sources */, 88CDE04F2A2508E900114294 /* TempTrackApp.swift in Sources */, - 88CDE0722A28AEB900114294 /* TemperatureDataTransferDelegate.swift in Sources */, 88CDE0782A28AF2C00114294 /* TemperatureSensor.swift in Sources */, 88CDE0682A2698B400114294 /* TemperatureStorage.swift in Sources */, 88404DE92A31F7D500D30244 /* Data+Extensions.swift in Sources */, @@ -521,22 +509,6 @@ minimumVersion = 4.0.0; }; }; - 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/weitieda/bottom-sheet"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; - E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/stephencelis/SQLite.swift"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.14.1; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -550,16 +522,6 @@ package = 88CDE0642A25D08F00114294 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; productName = SFSafeSymbols; }; - 88CDE06A2A2899C900114294 /* BottomSheet */ = { - isa = XCSwiftPackageProductDependency; - package = 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */; - productName = BottomSheet; - }; - E253A9262A2CA48A00EC6B28 /* SQLite */ = { - isa = XCSwiftPackageProductDependency; - package = E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */; - productName = SQLite; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 88CDE0432A2508E800114294 /* Project object */; diff --git a/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5dfd079..b4fdc68 100644 --- a/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,31 +9,13 @@ "version" : "2.0.0" } }, - { - "identity" : "bottom-sheet", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weitieda/bottom-sheet", - "state" : { - "revision" : "6b21007153365235418f3943a960a1f9546592e0", - "version" : "1.0.12" - } - }, { "identity" : "sfsafesymbols", "kind" : "remoteSourceControl", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "state" : { - "revision" : "2bcd249b49178247e6b52bac7d67d6e338a40cee", - "version" : "4.1.0" - } - }, - { - "identity" : "sqlite.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/stephencelis/SQLite.swift", - "state" : { - "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", - "version" : "0.14.1" + "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c", + "version" : "4.1.1" } } ], diff --git a/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index 4945cb5..0e9a75d 100644 Binary files a/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate and b/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TempTrack/Bluetooth/BluetoothClient.swift b/TempTrack/Bluetooth/BluetoothClient.swift index 63958cc..d023516 100644 --- a/TempTrack/Bluetooth/BluetoothClient.swift +++ b/TempTrack/Bluetooth/BluetoothClient.swift @@ -3,15 +3,18 @@ import SwiftUI final class BluetoothClient: ObservableObject { - weak var delegate: TemperatureDataTransferDelegate? - private let updateInterval = 3.0 + + private let minimumOffsetToUpdateDeviceClock = 5.0 private let connection = DeviceManager() + + private let storage: TemperatureStorage - init(deviceInfo: DeviceInfo? = nil) { - connection.delegate = self + init(storage: TemperatureStorage, deviceInfo: DeviceInfo? = nil) { + self.storage = storage self.deviceInfo = deviceInfo + connection.delegate = self } func connect() -> Bool { @@ -111,16 +114,17 @@ final class BluetoothClient: ObservableObject { // MARK: Device time private func updateDeviceTimeIfNeeded() { - guard let info = deviceInfo else { + guard let deviceInfo else { return } - guard !info.hasDeviceStartTimeSet else { + guard !deviceInfo.hasDeviceStartTimeSet || deviceInfo.clockOffset > minimumOffsetToUpdateDeviceClock else { return } + guard !openRequests.contains(where: { if case .setDeviceStartTime = $0 { return true }; return false }) else { return } - let time = info.deviceStartTime.seconds + let time = deviceInfo.deviceStartTime.seconds addRequest(.setDeviceStartTime(deviceStartTimeSeconds: time)) print("Setting device start time to \(time) s (\(Date().seconds) current)") } @@ -144,7 +148,6 @@ final class BluetoothClient: ObservableObject { let transfer = TemperatureDataTransfer(info: info) runningTransfer = transfer - runningTransfer?.delegate = delegate let next = transfer.nextRequest() addRequest(next) return true @@ -171,12 +174,13 @@ final class BluetoothClient: ObservableObject { return } self.deviceInfo = newInfo - guard let runningTransfer else { - return + if let runningTransfer { + runningTransfer.update(info: newInfo) + let next = runningTransfer.nextRequest() + addRequest(next) + } else if newInfo.numberOfStoredMeasurements > 0 { + collectRecordedData() } - runningTransfer.update(info: newInfo) - let next = runningTransfer.nextRequest() - addRequest(next) } } @@ -226,13 +230,24 @@ extension BluetoothClient: DeviceManagerDelegate { case .getRecordingData(let offset, let count): didReceive(data: payload, offset: offset, count: count) case .clearRecordingBuffer: - runningTransfer?.completeTransfer() - runningTransfer = nil + didClearDeviceStorage() + case .setDeviceStartTime: print("Device time set") break } } + + private func didClearDeviceStorage() { + guard let runningTransfer else { + return + } + runningTransfer.completeTransfer() + storage.add(runningTransfer.measurements) + self.runningTransfer = nil + + updateDeviceTimeIfNeeded() + } func deviceManager(didChangeState state: DeviceState) { DispatchQueue.main.async { diff --git a/TempTrack/ContentView.swift b/TempTrack/ContentView.swift index 8ae2455..d7a4501 100644 --- a/TempTrack/ContentView.swift +++ b/TempTrack/ContentView.swift @@ -1,6 +1,5 @@ import SwiftUI import SFSafeSymbols -import BottomSheet struct ContentView: View { @@ -144,9 +143,11 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { + let storage = TemperatureStorage(lastMeasurements: TemperatureMeasurement.mockData) + let client = BluetoothClient(storage: storage, deviceInfo: .mock) ContentView() - .environmentObject(TemperatureStorage(lastMeasurements: TemperatureMeasurement.mockData)) - .environmentObject(BluetoothClient(deviceInfo: .mock)) + .environmentObject(storage) + .environmentObject(client) } } diff --git a/TempTrack/Storage/TemperatureStorage.swift b/TempTrack/Storage/TemperatureStorage.swift index 694329d..3913c38 100644 --- a/TempTrack/Storage/TemperatureStorage.swift +++ b/TempTrack/Storage/TemperatureStorage.swift @@ -11,10 +11,15 @@ final class TemperatureStorage: ObservableObject { in: .userDomainMask, appropriateFor: nil, create: true) } - + @AppStorage("newestDate") private var newestMeasurementTime: Int = 0 - + + /** + The date of the latest measurement. + + Incoming data older than this date will be rejected to prevent duplicate measurements + */ private var newestMeasurementDate: Date { get { Date(seconds: newestMeasurementTime) @@ -30,8 +35,6 @@ final class TemperatureStorage: ObservableObject { @Published var dailyMeasurementCounts: [MeasurementDailyCount] = [] - private var unsavedMeasurements: [TemperatureMeasurement] = [] - private let fileNameFormatter: DateFormatter private let storageFolder: URL @@ -98,6 +101,12 @@ final class TemperatureStorage: ObservableObject { .filter { $0.date >= startDate } recentMeasurements = yesterdayValues + todayValues } + + private func updateLastMeasurements(_ measurements: [TemperatureMeasurement]) { + let startDate = Date().addingTimeInterval(-lastValueInterval).seconds + let new = recentMeasurements + measurements + recentMeasurements = Array(new.drop { $0.id < startDate }) + } private func loadMeasurements(for date: Date) -> [TemperatureMeasurement] { loadMeasurements(from: fileName(for: date)) @@ -123,14 +132,20 @@ final class TemperatureStorage: ObservableObject { return [] } } - - func save() { - for (dateIndex, values) in unsavedMeasurements.splitByDate() { + + func add(_ measurements: [TemperatureMeasurement]) { + let lastDate = self.newestMeasurementDate.seconds + let newValues = measurements + .filter { $0.id > lastDate } + .splitByDate() + + for (dateIndex, values) in newValues { let count = saveNew(values, for: dateIndex) - print("Day \(dateIndex): \(count) of \(values.count) saved") + setDailyCount(count, for: dateIndex) + print("Day \(dateIndex): \(count) values") } - unsavedMeasurements = [] saveDailyCounts() + updateLastMeasurements(measurements) } /** @@ -138,21 +153,9 @@ final class TemperatureStorage: ObservableObject { */ private func saveNew(_ measurements: [TemperatureMeasurement], for dateIndex: Int) -> Int { let fileName = fileName(for: dateIndex) - var existing = loadMeasurements(from: fileName) - guard !existing.isEmpty else { - save(measurements, for: fileName) - setDailyCount(measurements.count, for: dateIndex) - return measurements.count - } - var inserted = 0 - for value in measurements { - if existing.insert(value) { - inserted += 1 - } - } - save(existing, for: fileName) - setDailyCount(existing.count, for: dateIndex) - return inserted + let values = loadMeasurements(from: fileName) + measurements + save(values, for: fileName) + return values.count } private func save(_ measurements: [TemperatureMeasurement], for fileName: String) { @@ -239,23 +242,6 @@ final class TemperatureStorage: ObservableObject { } -extension TemperatureStorage: TemperatureDataTransferDelegate { - - func didReceiveRecording(_ measurement: TemperatureMeasurement) { - // Add to unsaved measurements - if unsavedMeasurements.insert(measurement) { - incrementCount(for: measurement.date.dateIndex) - } - - // Add to last measurements - recentMeasurements.insert(measurement) - } - - func saveAfterTransfer() { - save() - } -} - private extension Array where Element == TemperatureMeasurement { @discardableResult diff --git a/TempTrack/TempTrackApp.swift b/TempTrack/TempTrackApp.swift index c598434..f56400d 100644 --- a/TempTrack/TempTrackApp.swift +++ b/TempTrack/TempTrackApp.swift @@ -1,7 +1,9 @@ import SwiftUI -let storage = TemperatureStorage() -let bluetoothClient = BluetoothClient() +private let storage = TemperatureStorage() +private let bluetoothClient: BluetoothClient = { + .init(storage: storage) +}() @main struct TempTrackApp: App { @@ -11,9 +13,6 @@ struct TempTrackApp: App { ContentView() .environmentObject(storage) .environmentObject(bluetoothClient) - .onAppear { - bluetoothClient.delegate = storage - } } } } diff --git a/TempTrack/Temperature/TemperatureDataTransfer.swift b/TempTrack/Temperature/TemperatureDataTransfer.swift index 0037bb8..3bf1588 100644 --- a/TempTrack/Temperature/TemperatureDataTransfer.swift +++ b/TempTrack/Temperature/TemperatureDataTransfer.swift @@ -6,8 +6,6 @@ final class TemperatureDataTransfer { private let interval: Int - weak var delegate: TemperatureDataTransferDelegate? - private var dataBuffer: Data = Data() private(set) var currentByteIndex = 0 @@ -17,8 +15,11 @@ final class TemperatureDataTransfer { private(set) var blockSize: Int private var numberOfRecordingsInCurrentTransfer = 0 - - private(set) var lastRecording: TemperatureMeasurement = .init(sensor0: .notFound, sensor1: .notFound, date: .now) + + var measurements: [TemperatureMeasurement] = [] + + /// The last temperatures to calculate relative values + private var lastRecording: TemperatureMeasurement = .init(sensor0: .notFound, sensor1: .notFound, date: .now) private var dateOfNextRecording: Date { startDateOfCurrentTransfer.addingTimeInterval(TimeInterval(numberOfRecordingsInCurrentTransfer * interval)) @@ -78,7 +79,6 @@ final class TemperatureDataTransfer { func completeTransfer() { processBytes() - delegate?.saveAfterTransfer() } private func addRelative(byte: UInt8) { @@ -100,7 +100,7 @@ final class TemperatureDataTransfer { lastRecording.sensor1 = measurement.sensor1 } lastRecording.id = measurement.id - delegate?.didReceiveRecording(measurement) + measurements.append(measurement) } private func convertTemp(value: UInt8, relativeTo previous: TemperatureValue) -> TemperatureValue { diff --git a/TempTrack/Temperature/TemperatureDataTransferDelegate.swift b/TempTrack/Temperature/TemperatureDataTransferDelegate.swift deleted file mode 100644 index 22165ad..0000000 --- a/TempTrack/Temperature/TemperatureDataTransferDelegate.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -protocol TemperatureDataTransferDelegate: AnyObject { - - func didReceiveRecording(_ measurement: TemperatureMeasurement) - - func saveAfterTransfer() -} diff --git a/TempTrack/Temperature/TemperatureMeasurement.swift b/TempTrack/Temperature/TemperatureMeasurement.swift index 2ec490f..1bb8f02 100644 --- a/TempTrack/Temperature/TemperatureMeasurement.swift +++ b/TempTrack/Temperature/TemperatureMeasurement.swift @@ -5,7 +5,8 @@ struct TemperatureMeasurement: Identifiable { var sensor0: TemperatureValue var sensor1: TemperatureValue - + + /// The seconds since 1970 var id: Int var date: Date { @@ -18,7 +19,7 @@ struct TemperatureMeasurement: Identifiable { } var secondsToNow: Int { - Date().seconds - id + id - Date().seconds } var maximumValue: Double? { @@ -172,7 +173,7 @@ extension TemperatureMeasurement { TemperatureMeasurement( sensor0: .init(value: $0.element.0), sensor1: .init(value: $0.element.1), - id: seconds + $0.offset * 60) + id: seconds + ($0.offset - temps.count) * 60) } }() }