first commit

This commit is contained in:
fototeddy
2026-09-04 21:30:51 +02:00
commit da64b1b6bd
160 changed files with 20664 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
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
}
}