import Foundation /// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne /// Fahrzeug und ohne Bluetooth prüfen lassen. /// /// Nur in Debug-Builds, auf zwei Wegen einzuschalten: /// /// * Umgebungsvariable `CAMPER_DEMO=1` beim Start – praktisch im Simulator: /// /// xcrun simctl launch --terminate-running-process \ /// booted de.fritob.VanControl /// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor /// /// * Schalter in den Einstellungen (`SettingsView`) – der einzige Weg auf /// einem lokal installierten Build ohne Xcode-Verbindung, da sich dort /// keine Umgebungsvariable setzen lässt. Wirkt erst nach einem Neustart der /// App, weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start /// lesen. /// /// Im normalen Betrieb wird hiervon nichts ausgeführt. enum DemoData { /// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`. static let enabledKey = "demoModeEnabled" static var isEnabled: Bool { #if DEBUG if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true } return UserDefaults.standard.bool(forKey: enabledKey) #else return false #endif } static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen", symbol: "box.truck", trackWidth: 1.85, wheelbase: 3.50) static let secondProfile = Profile( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000C2")!, name: "Wohnwagen", symbol: "car.side") static var profiles: [Profile] { [mainProfile, secondProfile] } static let booster = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000B0")!, name: "Ladebooster", role: .chargeBooster, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000B1")!) static let victronSolar = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-000000000050")!, name: "Solar Dach", role: .victronSolarCharger, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-000000000051")!) static let battery = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000A0")!, name: "Bulltron 200 Ah", role: .bms, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000A1")!) static let caravanVictronSolar = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000C3")!, name: "Solar Wohnwagen", role: .victronSolarCharger, profileID: secondProfile.id, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000C4")!) static let fridge = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000F0")!, name: "Kühlbox", role: .fridge, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000F1")!) static let level = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000E0")!, name: "Nivellierung", role: .leveling, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000E1")!) static let votronicSolar = ConfiguredDevice( id: UUID(uuidString: "00000000-0000-0000-0000-0000000000D0")!, name: "VotronicSolarESP", role: .votronicSolar, profileID: Profile.defaultID, peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000D1")!) static var devices: [ConfiguredDevice] { [victronSolar, booster, battery, fridge, level, votronicSolar, caravanVictronSolar] } /// Leicht schräg stehend, damit die Libelle etwas zu zeigen hat. static var levelState: LevelState { var state = LevelState() state.pitch = 1.8 state.roll = -0.9 state.pitchOffset = 0.4 state.rollOffset = -0.2 return state } /// Zustand der Demo-Kühlbox: eingeschaltet, Eco, Soll 4 °C, ist 6 °C. static var fridgeState: AlpicoolState { var state = AlpicoolState() state.apply(AlpicoolProtocol.Frame(command: 0x01, payload: [ 0x00, 0x01, 0x01, 0x00, 0x04, 0x14, 0xE2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0x0C, 0x07, ])) return state } /// Mittags, gute Sonne. static var votronicSolarESPState: VotronicSolarESPState { var state = VotronicSolarESPState() state.batteryVoltage = 13.9 state.pvVoltage = 19.4 state.pvCurrent = 6.2 state.pvPower = 120 state.controllerTemperature = 34 state.isBatteryCharging = true state.isControllerActive = true state.isCurrentLimited = false return state } static func snapshots() -> [DeviceSnapshot] { var victronSolarSnapshot = DeviceSnapshot(deviceID: victronSolar.id, timestamp: Date(), rssi: -58) victronSolarSnapshot.state = "Konstantspannung (Absorption)" victronSolarSnapshot.metrics = [ Metric("pv_power", "PV-Leistung", 284, unit: "W", precision: 0, primary: true), Metric("battery_power", "Ladeleistung", 262, unit: "W", precision: 0), Metric("battery_voltage", "Batteriespannung", 14.12, unit: "V", precision: 2), Metric("battery_current", "Ladestrom", 18.6, unit: "A", precision: 1), Metric("yield_today", "Ertrag heute", 1.34, unit: "kWh", precision: 2), ] var boosterSnapshot = DeviceSnapshot(deviceID: booster.id, timestamp: Date(), rssi: -71) boosterSnapshot.state = "Aus" boosterSnapshot.offReasons = ["Keine Eingangsspannung"] boosterSnapshot.metrics = [ Metric("output_voltage", "Ausgang (Aufbaubatterie)", 14.09, unit: "V", precision: 2, primary: true), Metric("input_voltage", "Eingang (Starterbatterie)", 12.42, unit: "V", precision: 2), ] let cells = [3.412, 3.418, 3.409, 3.421] var batterySnapshot = DeviceSnapshot(deviceID: battery.id, timestamp: Date(), rssi: -64) batterySnapshot.state = "Lädt" batterySnapshot.cellVoltages = cells batterySnapshot.temperatures = [21, 22] batterySnapshot.metrics = [ Metric("soc", "Ladezustand", 78.4, unit: "%", precision: 1, primary: true), Metric("voltage", "Spannung", 13.66, unit: "V", precision: 2), Metric("current", "Strom", 18.6, unit: "A", precision: 1), Metric("power", "Leistung", 254, unit: "W", precision: 0), Metric("capacity", "Restkapazität", 156.8, unit: "Ah", precision: 1), Metric("cell_delta", "Zell-Differenz", 12, unit: "mV", precision: 0), Metric("cell_max", "Höchste Zelle (Zelle 4)", 3.421, unit: "V", precision: 3), Metric("cell_min", "Niedrigste Zelle (Zelle 3)", 3.409, unit: "V", precision: 3), Metric("temp_max", "Temperatur", 22, unit: "°C", precision: 0), Metric("cycles", "Ladezyklen", 143, unit: "", precision: 0), ] var caravanSnapshot = DeviceSnapshot(deviceID: caravanVictronSolar.id, timestamp: Date(), rssi: -77) caravanSnapshot.state = "Erhaltung (Float)" caravanSnapshot.metrics = [ Metric("pv_power", "PV-Leistung", 62, unit: "W", precision: 0, primary: true), Metric("battery_voltage", "Batteriespannung", 13.62, unit: "V", precision: 2), Metric("battery_current", "Ladestrom", 4.4, unit: "A", precision: 1), ] return [victronSolarSnapshot, boosterSnapshot, batterySnapshot, fridgeState.snapshot(deviceID: fridge.id, rssi: -66), levelState.snapshot(deviceID: level.id, rssi: -70), votronicSolarESPState.snapshot(deviceID: votronicSolar.id, rssi: -58), caravanSnapshot] } /// Ein paar Stunden Verlauf, damit die Grafik etwas zeigt. static func history(for deviceID: UUID, around value: Double) -> [HistorySample] { let now = Date() return (0..<80).reversed().map { step in let t = Double(step) let wave = sin(t / 9) * value * 0.18 + cos(t / 23) * value * 0.07 return HistorySample(time: now.addingTimeInterval(-t * 60), value: max(0, value + wave)) } } }