forked from fritob/Camper-Monitor
Begleitet das Einparken: verfolgt die Neigung über die Zeit, meldet ob es besser oder schlechter wird, und erinnert an den flachsten Punkt - "vor 4 Sekunden stand das Fahrzeug 0,8 Grad flacher". Bei Erreichen der Toleranz gibt es eine Vibration, das Display bleibt währenddessen wach. Bewusst ohne Positionsbestimmung. Aus einem MEMS-Sensor lässt sich keine brauchbare Strecke ableiten: der Fehler wächst beim zweifachen Integrieren quadratisch mit der Zeit, und im Schritttempo gehen die tatsächlichen Beschleunigungen im Rauschen unter. Gebraucht wird sie auch nicht - beim Einparken lautet die Frage nie "wo stehe ich", sondern "wird es besser". Das steckt vollständig im zeitlichen Verlauf der Neigung, ohne jede Annahme über das Gelände. Die vorhandene Verlaufsaufzeichnung war mit einem Punkt alle fünf Sekunden zu grob; der Assistent führt einen eigenen Ringpuffer, der nur läuft solange die Ansicht offen ist. Sind Spurweite und Radstand hinterlegt, kommt die nötige Höhe der Auffahrkeile dazu. Das ist reine Geometrie und damit exakt. Die Masse stehen im Fahrzeugprofil. Dabei fiel auf, dass sich vorhandene Fahrzeuge praktisch nicht bearbeiten liessen: das ging nur über eine Wischgeste, die niemand findet - erst recht nicht, wenn dort jetzt die Masse einzutragen sind. Die Zeile hat nun einen sichtbaren Knopf dafür, wie bei den WLAN-Einstellungen: antippen wählt aus, das Zeichen daneben öffnet die Bearbeitung. Ausserdem übernimmt der Assistent den anliegenden Messwert beim Öffnen. Sonst stand dort bis zur nächsten Messung "Warte auf Messwerte", obwohl längst welche vorlagen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
148 lines
6.8 KiB
Swift
148 lines
6.8 KiB
Swift
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 und nur, wenn beim Start `CAMPER_DEMO=1` gesetzt ist:
|
|
///
|
|
/// xcrun simctl launch --terminate-running-process \
|
|
/// booted de.fritob.CamperMonitor
|
|
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
|
|
///
|
|
/// Im normalen Betrieb wird hiervon nichts ausgeführt.
|
|
enum DemoData {
|
|
|
|
static var isEnabled: Bool {
|
|
#if DEBUG
|
|
return ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1"
|
|
#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 var devices: [ConfiguredDevice] {
|
|
[solar, booster, battery, fridge, level, caravanSolar]
|
|
}
|
|
|
|
/// 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))
|
|
}
|
|
}
|
|
}
|