import Foundation /// Welche Rolle ein Gerät im Camper spielt. Bestimmt Icon, Sortierung und /// welche Kennzahl als "Hauptwert" auf der Kachel gross dargestellt wird. enum DeviceRole: String, Codable, CaseIterable, Identifiable, Sendable { case chargeBooster case solarCharger case batteryMonitor case bms case fridge var id: String { rawValue } var title: String { switch self { case .chargeBooster: return "Ladebooster" case .solarCharger: return "Solarladeregler" case .batteryMonitor: return "Batteriemonitor" case .bms: return "Batterie / BMS" case .fridge: return "Kühlbox" } } var symbol: String { switch self { case .chargeBooster: return "bolt.car" case .solarCharger: return "sun.max" case .batteryMonitor: return "gauge.with.dots.needle.bottom.50percent" case .bms: return "battery.100percent.bolt" case .fridge: return "refrigerator" } } /// Victron-Geräte werden passiv über das Advertisement gelesen, das Daly BMS /// braucht eine echte GATT-Verbindung. var transport: DeviceTransport { switch self { case .bms, .fridge: return .connect default: return .advertisement } } } enum DeviceTransport: Sendable { /// Passives Mitlesen der BLE-Werbedaten (Victron Instant Readout). case advertisement /// Verbindungsaufbau, Kommando schreiben, Antwort per Notify lesen (Daly). case connect } /// Wie viele Kühlzonen eine Box hat. Manche Einzonen-Boxen senden den langen /// Datensatz einer Doppelzonen-Box mit, weshalb die Erkennung übersteuerbar ist. enum FridgeZoneMode: String, Codable, CaseIterable, Identifiable, Sendable { case automatic case single case dual var id: String { rawValue } var title: String { switch self { case .automatic: return "Automatisch" case .single: return "Eine Zone" case .dual: return "Zwei Zonen" } } } /// Ein vom Nutzer eingerichtetes Gerät. Der Victron-Schlüssel liegt nicht hier, /// sondern in der Keychain (siehe `KeychainStore`). struct ConfiguredDevice: Identifiable, Codable, Hashable, Sendable { var id: UUID var name: String var role: DeviceRole /// Zu welchem Fahrzeug das Gerät gehört. var profileID: UUID /// CoreBluetooth-Identifier des Peripherals. Pro iPhone stabil, aber nicht /// geräteübergreifend – deshalb wird beim Einrichten neu gescannt. var peripheralID: UUID /// Zuletzt gesehener Advertised Name, nur zur Wiedererkennung in der UI. var advertisedName: String? /// Nur für Kühlboxen. var fridgeZoneMode: FridgeZoneMode = .automatic init(id: UUID = UUID(), name: String, role: DeviceRole, profileID: UUID, peripheralID: UUID, advertisedName: String? = nil) { self.id = id self.name = name self.role = role self.profileID = profileID self.peripheralID = peripheralID self.advertisedName = advertisedName } /// Geräte, die vor der Profilverwaltung angelegt wurden, haben noch keine /// Zuordnung – die landen im Standardprofil. init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) name = try container.decode(String.self, forKey: .name) role = try container.decode(DeviceRole.self, forKey: .role) profileID = try container.decodeIfPresent(UUID.self, forKey: .profileID) ?? Profile.defaultID peripheralID = try container.decode(UUID.self, forKey: .peripheralID) advertisedName = try container.decodeIfPresent(String.self, forKey: .advertisedName) fridgeZoneMode = try container.decodeIfPresent(FridgeZoneMode.self, forKey: .fridgeZoneMode) ?? .automatic } }