forked from fritob/Camper-Monitor
Der Einstellungen-Schalter für den Demo-Modus war in #if DEBUG eingeschlossen und damit in Archiven (Release-Konfiguration, wie sie Product → Archive und damit jeder TestFlight-Build verwendet) komplett herauskompiliert. TestFlight-Tester konnten den Demo-Modus dadurch nicht nutzen. DemoData.isEnabled und der Schalter in SettingsView sind jetzt konfigurationsunabhängig; nur die CAMPER_DEMO-Umgebungsvariable bleibt auf Debug-Builds beschränkt, da sie ohnehin nur über Xcode/simctl gesetzt werden kann. Build (Debug + Release) und Tests grün; per strings(1) im Release-Binary verifiziert, dass der Demo-Modus-Code tatsächlich mitkompiliert wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
176 lines
8.2 KiB
Swift
176 lines
8.2 KiB
Swift
import Foundation
|
||
|
||
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
|
||
/// Fahrzeug und ohne Bluetooth prüfen lassen.
|
||
///
|
||
/// Auf zwei Wegen einzuschalten:
|
||
///
|
||
/// * Schalter in den Einstellungen (`SettingsView`) – in jeder
|
||
/// Build-Konfiguration verfügbar, auch in TestFlight- und
|
||
/// App-Store-Builds. Der Weg für jeden lokal installierten Build ohne
|
||
/// Xcode-Verbindung. Wirkt erst nach einem Neustart der App, weil
|
||
/// `DeviceStore` und `BluetoothManager` den Stand nur beim Start lesen.
|
||
/// * Umgebungsvariable `CAMPER_DEMO=1` beim Start – nur in Debug-Builds,
|
||
/// praktisch im Simulator:
|
||
///
|
||
/// xcrun simctl launch --terminate-running-process \
|
||
/// booted de.fritob.VanControl
|
||
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
|
||
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 }
|
||
#endif
|
||
return UserDefaults.standard.bool(forKey: enabledKey)
|
||
}
|
||
|
||
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))
|
||
}
|
||
}
|
||
}
|