90 lines
2.5 KiB
Swift
90 lines
2.5 KiB
Swift
import Foundation
|
||
|
||
/// Eine einzelne Messgrösse in einer bereits formatierten Form.
|
||
struct Metric: Identifiable, Hashable, 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, 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] = []
|
||
|
||
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, 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
|
||
}
|
||
}
|
||
}
|