Basic display of info

This commit is contained in:
Christoph Hagen
2023-06-03 08:15:00 +02:00
parent 0f97bfc316
commit 6e0910e47f
16 changed files with 1417 additions and 12 deletions

View 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
}
}

View File

@@ -0,0 +1,6 @@
import Foundation
protocol TemperatureDataTransferDelegate: AnyObject {
func didReceiveRecording(_ measurement: TemperatureMeasurement)
}

View File

@@ -0,0 +1,10 @@
import Foundation
struct TemperatureMeasurement {
var sensor0: TemperatureValue
var sensor1: TemperatureValue
var date: Date
}

View 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"
}
}

View 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"
}
}
}