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
+97
View File
@@ -0,0 +1,97 @@
import Foundation
/// Eine einzelne Messgrösse in einer bereits formatierten Form.
struct Metric: Identifiable, Hashable, Codable, Sendable {
let key: String
let label: String
let value: Double?
let unit: String
/// Nachkommastellen für die Anzeige.
let precision: Int
/// Wird auf der Kachel gross dargestellt.
let isPrimary: Bool
var id: String { key }
init(_ key: String,
_ label: String,
_ value: Double?,
unit: String,
precision: Int = 2,
primary: Bool = false) {
self.key = key
self.label = label
self.value = value
self.unit = unit
self.precision = precision
self.isPrimary = primary
}
var formatted: String {
guard let value else { return "" }
return String(format: "%.\(precision)f", value)
}
var formattedWithUnit: String {
guard value != nil else { return "" }
return unit.isEmpty ? formatted : "\(formatted) \(unit)"
}
}
/// Der komplette, zuletzt empfangene Zustand eines Geräts.
struct DeviceSnapshot: Identifiable, Codable, Equatable, Sendable {
var id: UUID { deviceID }
var deviceID: UUID
var timestamp: Date
var metrics: [Metric] = []
/// z.B. "Bulk", "Float", "Aus"
var state: String?
/// Klartext einer aktiven Störung, sonst nil.
var fault: String?
/// Grund, warum das Gerät gerade nicht lädt (Victron Off-Reason).
var offReasons: [String] = []
var rssi: Int?
/// Einzelzellspannungen in Volt (nur BMS).
var cellVoltages: [Double] = []
/// Temperaturfühler in °C (nur BMS).
var temperatures: [Double] = []
/// Feste Angaben des Geräts, etwa Modell oder Seriennummer.
var info: [InfoItem] = []
struct InfoItem: Identifiable, Hashable, Codable, Sendable {
let label: String
let value: String
var id: String { label }
}
var primaryMetric: Metric? {
metrics.first(where: \.isPrimary) ?? metrics.first
}
var age: TimeInterval { Date().timeIntervalSince(timestamp) }
/// Werte älter als eine Minute gelten als veraltet Victron sendet etwa
/// jede Sekunde, das Daly wird alle paar Sekunden gepollt.
var isStale: Bool { age > 60 }
}
/// Verbindungszustand für die UI.
enum DeviceLinkState: Equatable, Codable, Sendable {
case idle
case searching
case connecting
case live
case needsKey
case failed(String)
var label: String {
switch self {
case .idle: return "Inaktiv"
case .searching: return "Suche…"
case .connecting: return "Verbinde…"
case .live: return "Live"
case .needsKey: return "Schlüssel fehlt"
case .failed(let m): return m
}
}
}