Files
VanAligneiOS/CamperMonitor/Store/DemoData.swift
fototeddyandClaude Sonnet 5 644c4c7767 Rename solar integration types to VotronicSolarESP
Renames every type and identifier we built for the solar charge
controller bridge from generic "Solar" naming to the product name
VotronicSolarESP: VanAlignSolarProtocol -> VotronicSolarESPProtocol,
SolarSession -> VotronicSolarESPSession, SolarState ->
VotronicSolarESPState, plus the dependent properties, parameters, and
user-facing discovery/empty-state strings.

Purely a Swift-side rename - the underlying BLE service/characteristic
UUIDs are untouched, so it has no effect on communicating with the
actual firmware. DeviceRole.solar's case name, and DemoData's device
instance variables, are left as-is since they describe the role/demo
device rather than this specific integration code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 20:22:04 +02:00

199 lines
9.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.CamperMonitor
/// # 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 solar = ConfiguredDevice(
id: UUID(uuidString: "00000000-0000-0000-0000-000000000050")!,
name: "Solar Dach", role: .solarCharger, 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 caravanSolar = ConfiguredDevice(
id: UUID(uuidString: "00000000-0000-0000-0000-0000000000C3")!,
name: "Solar Wohnwagen", role: .solarCharger, 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 solar = ConfiguredDevice(
id: UUID(uuidString: "00000000-0000-0000-0000-0000000000D0")!,
name: "Solar Dach", role: .solar, profileID: Profile.defaultID,
peripheralID: UUID(uuidString: "00000000-0000-0000-0000-0000000000D1")!)
static var devices: [ConfiguredDevice] {
[/*booster, battery, fridge, */level, solar/*, caravanSolar*/]
}
/// 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
}
/// Ein paar Stunden Verlauf je Kanal, damit die Grafik im Demo-Modus
/// sofort etwas zeigt statt erst nach ein paar Minuten Laufzeit.
static func votronicSolarESPHistory() -> DeviceHistory {
let now = Date()
func series(around value: Double, noise: Double) -> [HistorySample] {
(0..<120).reversed().map { step in
let t = Double(step)
let wave = sin(t / 14) * value * noise + cos(t / 31) * value * (noise / 2)
return HistorySample(time: now.addingTimeInterval(-t * 60),
value: max(0, value + wave))
}
}
return [
"pv_power": series(around: 120, noise: 0.35),
"pv_voltage": series(around: 19.4, noise: 0.08),
"pv_current": series(around: 6.2, noise: 0.3),
"battery_voltage": series(around: 13.9, noise: 0.03),
]
}
/// 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
}
static func snapshots() -> [DeviceSnapshot] {
var solarSnapshot = DeviceSnapshot(deviceID: solar.id, timestamp: Date(), rssi: -58)
solarSnapshot.state = "Konstantspannung (Absorption)"
solarSnapshot.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: caravanSolar.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 [solarSnapshot, boosterSnapshot, batterySnapshot,
fridgeState.snapshot(deviceID: fridge.id, rssi: -66),
levelState.snapshot(deviceID: level.id, rssi: -70),
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))
}
}*/
}