81 lines
3.0 KiB
Swift
81 lines
3.0 KiB
Swift
import Foundation
|
||
|
||
/// Zustand des Neigungsmessers.
|
||
struct LevelState: Equatable, Codable, Sendable {
|
||
/// Längsneigung: positiv bedeutet, das Heck steht höher als die Front.
|
||
/// Bereits auf die Einbaulage des Sensors umgerechnet.
|
||
var pitch: Double?
|
||
/// Querneigung: positiv bedeutet, die rechte Seite steht höher.
|
||
var roll: Double?
|
||
/// Wie der Sensor selbst meldet – nötig, um die Einbaulage zu bestimmen.
|
||
var rawPitch: Double?
|
||
var rawRoll: Double?
|
||
var pitchOffset: Double?
|
||
var rollOffset: Double?
|
||
|
||
var hasReading: Bool { pitch != nil || roll != nil }
|
||
|
||
/// Ob kalibriert wurde – oder nil, wenn das Gerät es nicht verrät.
|
||
///
|
||
/// Nicht jede Firmware stellt die Offsets bereit. Fehlen sie, heisst das
|
||
/// „unbekannt“ und nicht „nicht kalibriert“: eine Warnung, die sich nie
|
||
/// abstellen lässt, ist schlimmer als keine.
|
||
var calibrationState: Bool? {
|
||
guard let pitchOffset, let rollOffset else { return nil }
|
||
return abs(pitchOffset) > 0.001 || abs(rollOffset) > 0.001
|
||
}
|
||
|
||
/// Nur wahr, wenn es auch belegt ist.
|
||
var isCalibrated: Bool { calibrationState == true }
|
||
|
||
/// Nur wahr, wenn das Gerät ausdrücklich Nulloffsets meldet.
|
||
var isKnownUncalibrated: Bool { calibrationState == false }
|
||
|
||
/// Bis hierhin gilt das Fahrzeug als eben genug.
|
||
static let levelTolerance = 0.5
|
||
|
||
var isLevel: Bool {
|
||
guard let pitch, let roll else { return false }
|
||
return abs(pitch) <= Self.levelTolerance && abs(roll) <= Self.levelTolerance
|
||
}
|
||
|
||
/// Die grössere der beiden Abweichungen – das ist die, die man zuerst
|
||
/// ausgleichen will.
|
||
var largestDeviation: Double? {
|
||
switch (pitch, roll) {
|
||
case let (p?, r?): return max(abs(p), abs(r))
|
||
case let (p?, nil): return abs(p)
|
||
case let (nil, r?): return abs(r)
|
||
default: return nil
|
||
}
|
||
}
|
||
|
||
/// Was zu tun ist, in Worten. Beim Ausrichten schaut man aufs Handy,
|
||
/// nicht auf Vorzeichen.
|
||
var instruction: String? {
|
||
guard let pitch, let roll else { return nil }
|
||
if isLevel { return "Steht eben" }
|
||
var parts: [String] = []
|
||
if abs(pitch) > Self.levelTolerance {
|
||
parts.append(pitch > 0 ? "Heck steht höher" : "Front steht höher")
|
||
}
|
||
if abs(roll) > Self.levelTolerance {
|
||
parts.append(roll > 0 ? "rechts steht höher" : "links steht höher")
|
||
}
|
||
return parts.joined(separator: ", ")
|
||
}
|
||
|
||
func snapshot(deviceID: UUID, rssi: Int?) -> DeviceSnapshot {
|
||
var snapshot = DeviceSnapshot(deviceID: deviceID, timestamp: Date(), rssi: rssi)
|
||
snapshot.metrics = [
|
||
Metric("pitch", "Längsneigung", pitch, unit: "°", precision: 1, primary: true),
|
||
Metric("roll", "Querneigung", roll, unit: "°", precision: 1),
|
||
]
|
||
snapshot.state = instruction
|
||
if isKnownUncalibrated {
|
||
snapshot.offReasons = ["Nicht kalibriert"]
|
||
}
|
||
return snapshot
|
||
}
|
||
}
|