Basic display of info
This commit is contained in:
123
TempTrack/Temperature/TemperatureDataTransfer.swift
Normal file
123
TempTrack/Temperature/TemperatureDataTransfer.swift
Normal file
@@ -0,0 +1,123 @@
|
||||
import Foundation
|
||||
|
||||
final class TemperatureDataTransfer {
|
||||
|
||||
private let startDateOfCurrentTransfer: Date
|
||||
|
||||
private let interval: Int
|
||||
|
||||
weak var delegate: TemperatureDataTransferDelegate?
|
||||
|
||||
private var dataBuffer: Data = Data()
|
||||
|
||||
private(set) var currentByteIndex = 0
|
||||
|
||||
private(set) var size: Int
|
||||
|
||||
private(set) var blockSize: Int
|
||||
|
||||
private var numberOfRecordingsInCurrentTransfer = 0
|
||||
|
||||
private(set) var lastRecording: TemperatureMeasurement = .init(sensor0: .notFound, sensor1: .notFound, date: .now)
|
||||
|
||||
private var dateOfNextRecording: Date {
|
||||
startDateOfCurrentTransfer.addingTimeInterval(TimeInterval(numberOfRecordingsInCurrentTransfer * interval))
|
||||
}
|
||||
|
||||
var unprocessedByteCount: Int {
|
||||
dataBuffer.count
|
||||
}
|
||||
|
||||
var remainingBytesToTransfer: Int {
|
||||
size - currentByteIndex
|
||||
}
|
||||
|
||||
init(info: DeviceInfo) {
|
||||
self.interval = info.measurementInterval
|
||||
let recordingTime = info.numberOfMeasurements * info.measurementInterval
|
||||
self.startDateOfCurrentTransfer = info.nextMeasurement.addingTimeInterval(-TimeInterval(recordingTime))
|
||||
self.size = info.numberOfRecordedBytes
|
||||
self.blockSize = info.transferBlockSize
|
||||
}
|
||||
|
||||
func update(info: DeviceInfo) {
|
||||
self.size = info.numberOfRecordedBytes
|
||||
self.blockSize = info.transferBlockSize
|
||||
}
|
||||
|
||||
func nextRequest() -> BluetoothRequest {
|
||||
guard remainingBytesToTransfer > 0 else {
|
||||
return .clearRecordingBuffer(byteCount: size)
|
||||
}
|
||||
let chunkSize = min(remainingBytesToTransfer, blockSize)
|
||||
return .getRecordingData(offset: currentByteIndex, count: chunkSize)
|
||||
}
|
||||
|
||||
func add(data: Data, offset: Int, count: Int) {
|
||||
dataBuffer.append(data)
|
||||
currentByteIndex += data.count
|
||||
processBytes()
|
||||
}
|
||||
|
||||
private func processBytes() {
|
||||
while !dataBuffer.isEmpty {
|
||||
let byte = dataBuffer.removeFirst()
|
||||
guard (byte == 0xFF) else {
|
||||
addRelative(byte: byte)
|
||||
continue
|
||||
}
|
||||
guard dataBuffer.count >= 2 else {
|
||||
// Wait for more data
|
||||
return
|
||||
}
|
||||
let temp0 = TemperatureValue(byte: dataBuffer.removeFirst())
|
||||
let temp1 = TemperatureValue(byte: dataBuffer.removeFirst())
|
||||
add(sensor0: temp0, sensor1: temp1)
|
||||
}
|
||||
}
|
||||
|
||||
func completeTransfer() {
|
||||
processBytes()
|
||||
}
|
||||
|
||||
private func addRelative(byte: UInt8) {
|
||||
add(sensor0: convertTemp(value: byte >> 4, relativeTo: lastRecording.sensor0),
|
||||
sensor1: convertTemp(value: byte & 0x0F, relativeTo: lastRecording.sensor1))
|
||||
}
|
||||
|
||||
private func add(sensor0: TemperatureValue, sensor1: TemperatureValue) {
|
||||
let measurement = TemperatureMeasurement(
|
||||
sensor0: sensor0,
|
||||
sensor1: sensor1,
|
||||
date: dateOfNextRecording)
|
||||
|
||||
numberOfRecordingsInCurrentTransfer += 1
|
||||
if measurement.sensor0.isValid {
|
||||
lastRecording.sensor0 = measurement.sensor0
|
||||
}
|
||||
if measurement.sensor1.isValid {
|
||||
lastRecording.sensor1 = measurement.sensor1
|
||||
}
|
||||
lastRecording.date = measurement.date
|
||||
delegate?.didReceiveRecording(measurement)
|
||||
}
|
||||
|
||||
private func convertTemp(value: UInt8, relativeTo previous: TemperatureValue) -> TemperatureValue {
|
||||
if value == 0 {
|
||||
return .notFound
|
||||
}
|
||||
let newValue = previous.relativeValue - (Double(value) - 8) * 0.5
|
||||
return .value(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private extension TemperatureValue {
|
||||
|
||||
var relativeValue: Double {
|
||||
if case .value(let double) = self {
|
||||
return double
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
protocol TemperatureDataTransferDelegate: AnyObject {
|
||||
|
||||
func didReceiveRecording(_ measurement: TemperatureMeasurement)
|
||||
}
|
10
TempTrack/Temperature/TemperatureMeasurement.swift
Normal file
10
TempTrack/Temperature/TemperatureMeasurement.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
struct TemperatureMeasurement {
|
||||
|
||||
var sensor0: TemperatureValue
|
||||
|
||||
var sensor1: TemperatureValue
|
||||
|
||||
var date: Date
|
||||
}
|
94
TempTrack/Temperature/TemperatureSensor.swift
Normal file
94
TempTrack/Temperature/TemperatureSensor.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
import Foundation
|
||||
import SFSafeSymbols
|
||||
|
||||
struct TemperatureSensor {
|
||||
|
||||
let address: [UInt8]
|
||||
|
||||
let value: TemperatureValue
|
||||
|
||||
let date: Date
|
||||
|
||||
var optionalValue: Double? {
|
||||
value.optionalValue
|
||||
}
|
||||
|
||||
var hexAddress: String {
|
||||
String(format: "%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
address[0], address[1], address[2], address[3],
|
||||
address[4], address[5], address[6], address[7])
|
||||
}
|
||||
|
||||
var temperatureIcon: SFSymbol {
|
||||
TemperatureSensor.temperatureIcon(optionalValue)
|
||||
}
|
||||
|
||||
var temperatureText: String {
|
||||
value.text
|
||||
}
|
||||
|
||||
var updateText: String {
|
||||
date.timePassedText
|
||||
}
|
||||
|
||||
static func temperatureIcon(_ temp: Double?) -> SFSymbol {
|
||||
guard let temp else {
|
||||
return .thermometerMediumSlash
|
||||
}
|
||||
guard temp > 0 else {
|
||||
return .thermometerSnowflake
|
||||
}
|
||||
guard temp > 15 else {
|
||||
return .thermometerLow
|
||||
}
|
||||
guard temp > 25 else {
|
||||
return .thermometerMedium
|
||||
}
|
||||
return .thermometerHigh
|
||||
}
|
||||
}
|
||||
|
||||
extension TemperatureSensor {
|
||||
|
||||
init?(address: [UInt8], valueByte: UInt8, secondsAgo: UInt16) {
|
||||
guard address.contains(where: { $0 != 0 }) else {
|
||||
// Empty address
|
||||
return nil
|
||||
}
|
||||
self.address = address
|
||||
self.value = .init(byte: valueByte)
|
||||
self.date = Date().addingTimeInterval(-TimeInterval(secondsAgo))
|
||||
}
|
||||
}
|
||||
|
||||
extension Date {
|
||||
|
||||
var timePassedText: String {
|
||||
let secs = Int(-timeIntervalSinceNow.rounded())
|
||||
guard secs > 1 else {
|
||||
return "Now"
|
||||
}
|
||||
guard secs >= 60 else {
|
||||
return "\(secs) seconds ago"
|
||||
}
|
||||
let minutes = secs / 60
|
||||
guard minutes > 1 else {
|
||||
return "1 minute ago"
|
||||
}
|
||||
guard minutes >= 60 else {
|
||||
return "\(minutes) minutes ago"
|
||||
}
|
||||
let hours = minutes / 60
|
||||
guard hours > 1 else {
|
||||
return "1 hour ago"
|
||||
}
|
||||
guard hours >= 60 else {
|
||||
return "\(hours) hours ago"
|
||||
}
|
||||
let days = hours / 24
|
||||
guard days > 1 else {
|
||||
return "1 day ago"
|
||||
}
|
||||
return "\(days) days ago"
|
||||
}
|
||||
}
|
43
TempTrack/Temperature/TemperatureValue.swift
Normal file
43
TempTrack/Temperature/TemperatureValue.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
enum TemperatureValue {
|
||||
case notFound
|
||||
case invalidMeasurement
|
||||
case value(Double)
|
||||
|
||||
init(byte: UInt8) {
|
||||
switch byte {
|
||||
case 0:
|
||||
self = .notFound
|
||||
case 1:
|
||||
self = .invalidMeasurement
|
||||
default:
|
||||
self = .value(Double(byte) * 0.5 - 40)
|
||||
}
|
||||
}
|
||||
|
||||
var optionalValue: Double? {
|
||||
if case .value(let val) = self {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var isValid: Bool {
|
||||
if case .value = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .notFound:
|
||||
return "No sensor"
|
||||
case .invalidMeasurement:
|
||||
return "Invalid"
|
||||
case .value(let double):
|
||||
return "\(Int(double.rounded()))°C"
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user