import Foundation struct DeviceInfo { /** The maximum factor by which the device clock can run */ private let maximumTimeDilationFactor: Double = 0.01 /// The number of bytes recorded by the tracker let numberOfRecordedBytes: Int /// The number of measurements already performed let numberOfStoredMeasurements: Int /// The interval between measurements (in seconds) let measurementInterval: Int let sensor0: TemperatureSensor? let sensor1: TemperatureSensor? // MARK: Device time let wakeupReason: DeviceWakeCause let time: DeviceTime // MARK: Storage let storageSize: Int /// The maximum number of bytes which can be requested let transferBlockSize: Int var storageFillRatio: Double { Double(numberOfRecordedBytes) / Double(storageSize) } var storageFillPercentage: Int { Int((storageFillRatio * 100).rounded()) } var currentMeasurementStartTime: Date { time.nextMeasurement.addingTimeInterval(-Double(numberOfStoredMeasurements * measurementInterval)) } func estimatedTimeDilation(to previous: DeviceTime?) -> (start: Date, dilation: Double) { let trivialResult = (start: currentMeasurementStartTime, dilation: 1.0) guard let previous else { log.info("No previous device time to compare") return trivialResult } // Check if device was restarted in between guard time.secondsSincePowerOn >= previous.secondsSincePowerOn else { log.info("Device restarted (runtime decreased from \(previous.secondsSincePowerOn) to \(time.secondsSincePowerOn))") return trivialResult } let newMeasurementCount = time.totalNumberOfMeasurements - previous.totalNumberOfMeasurements guard newMeasurementCount >= 0 else { log.info("Device restarted (measurements decreased from \(previous.totalNumberOfMeasurements) to \(time.totalNumberOfMeasurements))") return trivialResult } guard newMeasurementCount > 0 else { log.warning("No new measurements to calculate time difference") return trivialResult } // Check that no measurements are missing // Calculate the difference between the expected time for the next measurement and the device time let deviceTimeDifference = Double(newMeasurementCount * measurementInterval) let expectedNextMeasurement = previous.nextMeasurement.addingTimeInterval(deviceTimeDifference) let timeDifference = time.nextMeasurement.timeIntervalSince(expectedNextMeasurement) let realTimeDifference = time.nextMeasurement.timeIntervalSince(previous.nextMeasurement) let timeDilation = realTimeDifference / deviceTimeDifference log.info("Device time dilation \(timeDilation) (difference \(timeDifference))") guard abs(timeDilation - 1.0) < maximumTimeDilationFactor else { log.warning("Device time too different from expected value (difference \(timeDifference) s)") return (currentMeasurementStartTime, 1.0) } return (previous.nextMeasurement, timeDilation) } } extension DeviceInfo { init(info: Data) throws { let date = Date() var data = info self.numberOfRecordedBytes = try data.decodeTwoByteInteger() let secondsUntilNextMeasurement = try data.decodeTwoByteInteger() self.measurementInterval = try data.decodeTwoByteInteger() self.numberOfStoredMeasurements = try data.decodeTwoByteInteger() let totalNumberOfMeasurements = try data.decodeFourByteInteger() self.transferBlockSize = try data.decodeTwoByteInteger() self.storageSize = try data.decodeTwoByteInteger() let secondsSincePowerOn = try data.decodeFourByteInteger() self.time = .init( date: date, secondsSincePowerOn: secondsSincePowerOn, totalNumberOfMeasurements: totalNumberOfMeasurements, secondsUntilNextMeasurement: secondsUntilNextMeasurement) let _ = try data.decodeFourByteInteger() self.sensor0 = try data.decodeSensor() self.sensor1 = try data.decodeSensor() self.wakeupReason = .init(rawValue: try data.getByte()) ?? .WAKEUP_UNDEFINED } } extension DeviceInfo { static var mock: DeviceInfo { .init( numberOfRecordedBytes: 123, numberOfStoredMeasurements: 234, measurementInterval: 60, sensor0: .init(address: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], value: .value(21.0), date: .now.addingTimeInterval(-2)), sensor1: .init(address: [0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], value: .value(19.0), date: .now.addingTimeInterval(-4)), wakeupReason: .WAKEUP_EXT0, time: .mock, storageSize: 10000, transferBlockSize: 180) } }