forked from fritob/Camper-Monitor
Beim ursprünglichen Merge übersehene Dateien nachziehen
Drei Dateien aus solar-integration wurden beim Zusammenführen mit dev_watch übersehen, weil sie fälschlich als "identisch zwischen den Branches" eingestuft wurden: * DemoData.isEnabled kannte nur noch den Umgebungsvariablen-Weg (CAMPER_DEMO=1). Der Schalter in den Einstellungen – der einzige Weg, Demo-Daten auf einem lokal installierten Build ohne Xcode-Verbindung einzuschalten – fehlte ganz, SettingsView.swift war nie zusammengeführt worden. * LevelSetupView.swift fehlte der komplette Abschnitt "Fahrzeuggrafik" – die Kachel-Auswahl zwischen den VehicleGraphicStyle-Varianten (Vanster/California). Das Feld existierte im Modell und wurde beim Rendern gelesen, war aber nirgends einstellbar. * DeviceCard.swift zeigte die Nivellierungs-Kachel wie jedes andere Gerät (Hauptwert + Nebenwerte) statt der grossen Front/Heck- und Links/Rechts-Anzeige mit Richtungsbuchstaben. Anlass: Im Testbuild fehlte deshalb sowohl der Demo-Modus als auch die Fahrzeuggrafik-Auswahl. Als Konsequenz systematisch alle beim Merge als "bereits identisch" eingestuften Dateien erneut gegengeprüft – keine weiteren Lücken gefunden. Build (App + Watch) und Protokoll-Tests grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ed026ce580
commit
b956324234
@@ -9,9 +9,21 @@ iOS-App, die per Bluetooth LE die Energieanlage im Wohnmobil ausliest:
|
|||||||
| Batterie-BMS (Bulltron/Daly, WattCycle) | GATT-Verbindung, alle 5 s abgefragt | SoC, Spannung, Strom, Restkapazität, alle Einzelzellspannungen, Zelldifferenz, Temperaturen, Zyklen, MOSFET-Status |
|
| Batterie-BMS (Bulltron/Daly, WattCycle) | GATT-Verbindung, alle 5 s abgefragt | SoC, Spannung, Strom, Restkapazität, alle Einzelzellspannungen, Zelldifferenz, Temperaturen, Zyklen, MOSFET-Status |
|
||||||
| Kompressor-Kühlbox (Alpicool und Baugleiche) | GATT-Verbindung, lesen **und** stellen | Ist- und Solltemperatur je Zone, Betriebsart, Kompressorstatus, Bordspannung, Batterieanzeige |
|
| Kompressor-Kühlbox (Alpicool und Baugleiche) | GATT-Verbindung, lesen **und** stellen | Ist- und Solltemperatur je Zone, Betriebsart, Kompressorstatus, Bordspannung, Batterieanzeige |
|
||||||
| Neigungsmesser (VanAlign Pro) | GATT-Verbindung | Längs- und Querneigung als Libelle, Kalibrierung aus der App |
|
| Neigungsmesser (VanAlign Pro) | GATT-Verbindung | Längs- und Querneigung als Libelle, Kalibrierung aus der App |
|
||||||
|
| Votronic-Solarladeregler (über eigene ESP32-Bridge) | GATT-Verbindung, im Takt abgefragt | PV-Leistung, PV-Spannung/-Strom, Batteriespannung, Reglertemperatur, Lade-/Reglerstatus |
|
||||||
|
|
||||||
Ein Victron SmartShunt/BMV wird ebenfalls unterstützt, falls später einer dazukommt.
|
Ein Victron SmartShunt/BMV wird ebenfalls unterstützt, falls später einer dazukommt.
|
||||||
|
|
||||||
|
Victron- und Votronic-Solarladeregler sind zwei getrennte Geräterollen, absichtlich
|
||||||
|
nicht zusammengelegt: Der Victron-Regler meldet sich passiv und verschlüsselt im
|
||||||
|
Advertisement, der Votronic-Regler hängt über eine eigene ESP32-Bridge (siehe
|
||||||
|
unten) an einer GATT-Verbindung – grundverschiedene Übertragungswege für
|
||||||
|
grundverschiedene Hardware. In Code und Einstellungen tragen beide deshalb
|
||||||
|
konsequent `victron`/`votronic` im Namen.
|
||||||
|
|
||||||
|
Der Neigungsmesser zeigt sich zusätzlich als **Live Activity** auf Sperrbildschirm,
|
||||||
|
Dynamic Island und – seit iOS 26 automatisch – im CarPlay-Dashboard, siehe
|
||||||
|
[Live Activity](#live-activity).
|
||||||
|
|
||||||
Dazu gehört eine **Apple-Watch-App**: Übersicht, Nivellierung samt
|
Dazu gehört eine **Apple-Watch-App**: Übersicht, Nivellierung samt
|
||||||
Ausrichtungs-Assistent mit Vibration und die Steuerung der Kühlbox am
|
Ausrichtungs-Assistent mit Vibration und die Steuerung der Kühlbox am
|
||||||
Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt
|
Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt
|
||||||
@@ -19,7 +31,9 @@ Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt
|
|||||||
[Apple Watch](#apple-watch).
|
[Apple Watch](#apple-watch).
|
||||||
|
|
||||||
Die Firmware des Neigungsmessers liegt mit im Projekt, siehe
|
Die Firmware des Neigungsmessers liegt mit im Projekt, siehe
|
||||||
[firmware/vanalign](firmware/vanalign/README.md).
|
[firmware/vanalign](firmware/vanalign/README.md). Der Votronic-Solarregler hängt
|
||||||
|
an einer zweiten, unabhängigen ESP32-Bridge – Firmware und BLE-Dienst dafür
|
||||||
|
liegen als `firmware/vanalign/esp32_ble_solar.yaml` im selben Ordner.
|
||||||
|
|
||||||
## Bauen und installieren
|
## Bauen und installieren
|
||||||
|
|
||||||
@@ -29,10 +43,12 @@ open /Users/fritob/GIT/Camper-Management/VanControl.xcodeproj
|
|||||||
|
|
||||||
Dann in Xcode:
|
Dann in Xcode:
|
||||||
|
|
||||||
1. Target `VanControl` → **Signing & Capabilities** → dein Apple-Team auswählen.
|
1. Bei allen vier Targets – `VanControl`, `VanControlWatch`,
|
||||||
Die Bundle-ID `de.fritob.VanControl` ggf. anpassen, falls sie schon vergeben ist.
|
`VanControlComplication`, `VanControlLiveActivityExtension` – unter
|
||||||
Dasselbe beim Target `VanControlWatch`; dessen Bundle-ID muss die des
|
**Signing & Capabilities** dein Apple-Team auswählen. Die Bundle-IDs ggf.
|
||||||
iPhones mit angehängtem `.watchkitapp` bleiben.
|
anpassen, falls sie schon vergeben sind; die der Watch-App muss die des
|
||||||
|
iPhones mit angehängtem `.watchkitapp` bleiben, die der Komplikation
|
||||||
|
zusätzlich `.levelwidget`.
|
||||||
2. iPhone per Kabel anschließen, oben als Ziel wählen, ⌘R.
|
2. iPhone per Kabel anschließen, oben als Ziel wählen, ⌘R.
|
||||||
|
|
||||||
Das Schema `VanControl` baut die Watch-App mit und bettet sie ein. Dafür
|
Das Schema `VanControl` baut die Watch-App mit und bettet sie ein. Dafür
|
||||||
@@ -181,10 +197,17 @@ hinweg gemerkt:
|
|||||||
In beiden Fällen: grün heißt eben (bis 0,5°), orange bis zwei Grad, darüber
|
In beiden Fällen: grün heißt eben (bis 0,5°), orange bis zwei Grad, darüber
|
||||||
rot. Dazu steht in Worten, welche Seite höher steht.
|
rot. Dazu steht in Worten, welche Seite höher steht.
|
||||||
|
|
||||||
Die Fahrzeugzeichnungen stammen aus dem Ursprungsprojekt VanAlign Pro. Sie
|
Die Fahrzeugansicht kennt mehrere Grafikstile (aktuell Vanster und
|
||||||
liegen als Schablonen im Asset-Katalog und werden je nach Abweichung
|
California), einstellbar je Fahrzeugprofil. Die Zeichnungen stammen aus dem
|
||||||
eingefärbt; die Helligkeit des Originals wurde dafür in Deckkraft übersetzt,
|
Ursprungsprojekt VanAlign Pro. Sie liegen als Schablonen im Asset-Katalog und
|
||||||
damit Fenster und Konturen beim Einfärben erhalten bleiben.
|
werden je nach Abweichung eingefärbt; die Helligkeit des Originals wurde
|
||||||
|
dafür in Deckkraft übersetzt, damit Fenster und Konturen beim Einfärben
|
||||||
|
erhalten bleiben.
|
||||||
|
|
||||||
|
Im Querformat rücken Libelle bzw. Fahrzeugansicht und die übrigen Angaben
|
||||||
|
nebeneinander statt untereinander – gedacht fürs Handy in der Halterung beim
|
||||||
|
Rangieren, wo man beiläufig hinschaut statt zu scrollen. Das gilt auch für
|
||||||
|
den Ausrichtungs-Assistenten weiter unten.
|
||||||
|
|
||||||
**Vor der ersten Nutzung die Einbaulage bestimmen.** Sitzt der Sensor quer,
|
**Vor der ersten Nutzung die Einbaulage bestimmen.** Sitzt der Sensor quer,
|
||||||
gedreht oder kopfüber, meldet er längs und quer vertauscht oder mit falschem
|
gedreht oder kopfüber, meldet er längs und quer vertauscht oder mit falschem
|
||||||
@@ -273,6 +296,27 @@ Längsneigung bei 3,50 m Radstand dazu, braucht die tiefste Ecke 16,1 cm, ihre
|
|||||||
Nachbarn 9,2 und 7,0 cm, und das höchststehende Rad bleibt liegen. Die Maße
|
Nachbarn 9,2 und 7,0 cm, und das höchststehende Rad bleibt liegen. Die Maße
|
||||||
stehen im Fahrzeugprofil, erreichbar über das ⓘ in der Fahrzeugliste.
|
stehen im Fahrzeugprofil, erreichbar über das ⓘ in der Fahrzeugliste.
|
||||||
|
|
||||||
|
### Live Activity
|
||||||
|
|
||||||
|
In der Nivellierungs-Ansicht lässt sich eine Live Activity einschalten, die
|
||||||
|
die Neigung auch bei gesperrtem Bildschirm zeigt: Sperrbildschirm, Dynamic
|
||||||
|
Island und – seit iOS 26, ohne eigenes CarPlay-Ziel nötig – automatisch im
|
||||||
|
CarPlay-Dashboard. Der Schalter ist deaktiviert, solange keine Messwerte
|
||||||
|
anliegen, sonst startete die Anzeige gleich mit einem veralteten Stand.
|
||||||
|
|
||||||
|
Die Aktualisierung ist bewusst auf höchstens einmal je Sekunde gedrosselt.
|
||||||
|
Der Neigungsmesser liefert deutlich öfter, und stiesse jede Messung sofort
|
||||||
|
eine Aktualisierung an, drosselt iOS das von sich aus zunehmend stärker –
|
||||||
|
sichtbar vor allem in CarPlay, dessen Dashboard ohnehin zurückhaltender
|
||||||
|
aktualisiert als Sperrbildschirm oder Dynamic Island. Innerhalb der
|
||||||
|
Sperrfrist eingehende Messwerte werden nicht verworfen, sondern der
|
||||||
|
jeweils neueste für ihr Ende vorgemerkt.
|
||||||
|
|
||||||
|
Ein kurzer Verbindungsabbruch beendet die Aktivität nicht sofort – erst nach
|
||||||
|
zwölf Sekunden ohne Verbindung, und meldet sich das Gerät vorher zurück,
|
||||||
|
läuft sie unverändert weiter. War sie wegen einer längeren Trennung wirklich
|
||||||
|
beendet, startet sie bei der nächsten Verbindung automatisch neu.
|
||||||
|
|
||||||
## Apple Watch
|
## Apple Watch
|
||||||
|
|
||||||
Die Uhr zeigt dasselbe wie das Dashboard, nur auf das eingedampft, wofür man
|
Die Uhr zeigt dasselbe wie das Dashboard, nur auf das eingedampft, wofür man
|
||||||
@@ -392,8 +436,9 @@ und die Prüfsummen beider Daly-Dialekte.
|
|||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
```
|
```
|
||||||
firmware/vanalign/ Firmware des Neigungsmessers (ESPHome)
|
firmware/vanalign/ Firmware der beiden ESP32 im Fahrzeug (ESPHome)
|
||||||
├── esp32_ble.yaml Neigungsmessung und Bluetooth-Schnittstelle
|
├── esp32_ble.yaml Neigungsmessung und Bluetooth-Schnittstelle
|
||||||
|
├── esp32_ble_solar.yaml Zweiter ESP32: Votronic-Solarregler über BLE
|
||||||
└── experimente/ Nicht für den Betrieb nötig
|
└── experimente/ Nicht für den Betrieb nötig
|
||||||
|
|
||||||
Shared/ In iPhone- und Watch-App übersetzt
|
Shared/ In iPhone- und Watch-App übersetzt
|
||||||
@@ -404,15 +449,22 @@ Shared/ In iPhone- und Watch-App übersetzt
|
|||||||
│ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart
|
│ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart
|
||||||
│ ├── DeviceSnapshot.swift Messwerte in Anzeigeform
|
│ ├── DeviceSnapshot.swift Messwerte in Anzeigeform
|
||||||
│ ├── LevelState.swift Neigung, Toleranz und Klartext dazu
|
│ ├── LevelState.swift Neigung, Toleranz und Klartext dazu
|
||||||
│ └── VictronCodes.swift Klartexte für Zustands-/Fehlercodes
|
│ ├── VictronCodes.swift Klartexte für Zustands-/Fehlercodes
|
||||||
|
│ └── VehicleGraphicStyle.swift Grafikstile der Fahrzeugansicht (Vanster/California)
|
||||||
├── Bluetooth/
|
├── Bluetooth/
|
||||||
│ ├── VanAlignProtocol.swift Neigungsmesser: Winkel und Kalibrierung
|
│ ├── VanAlignProtocol.swift Neigungsmesser: Winkel und Kalibrierung
|
||||||
│ └── LevelSession.swift Verbindung zum Neigungsmesser (iPhone und Uhr)
|
│ ├── LevelSession.swift Verbindung zum Neigungsmesser (iPhone und Uhr)
|
||||||
|
│ ├── VotronicSolarESPProtocol.swift Votronic-Solarregler: Charakteristiken, Statusbits
|
||||||
|
│ └── VotronicSolarESPSession.swift GATT-Verbindung zur Solar-Bridge, im Takt abgefragt
|
||||||
├── VehicleTilt.swift Überhöhung und Farben der Fahrzeugansicht
|
├── VehicleTilt.swift Überhöhung und Farben der Fahrzeugansicht
|
||||||
├── WheelLiftPlan.swift Keilhöhen als Draufsicht auf die vier Räder
|
├── WheelLiftPlan.swift Keilhöhen als Draufsicht auf die vier Räder
|
||||||
└── WatchLink/
|
└── WatchLink/
|
||||||
└── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr
|
└── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr
|
||||||
|
|
||||||
|
SharedActivity/ In Haupt-App und Live-Activity-Extension übersetzt
|
||||||
|
├── LevelActivityAttributes.swift Inhalt der Live Activity (ActivityKit)
|
||||||
|
└── LevelDirectionFormatting.swift "H 1.8°" statt Vorzeichen, für App und Extension gleich
|
||||||
|
|
||||||
VanControl/
|
VanControl/
|
||||||
├── Bluetooth/
|
├── Bluetooth/
|
||||||
│ ├── BluetoothManager.swift Zentraler Scan, Verbindungen, Verlauf
|
│ ├── BluetoothManager.swift Zentraler Scan, Verbindungen, Verlauf
|
||||||
@@ -425,8 +477,11 @@ VanControl/
|
|||||||
│ ├── WattCycleProtocol.swift WattCycle, Freischaltung und Auswertung
|
│ ├── WattCycleProtocol.swift WattCycle, Freischaltung und Auswertung
|
||||||
│ ├── AlpicoolProtocol.swift Kühlboxen: Auswertung und Stellbefehle
|
│ ├── AlpicoolProtocol.swift Kühlboxen: Auswertung und Stellbefehle
|
||||||
│ └── BMSSession.swift GATT-Verbindung, Protokollerkennung, Abfrage
|
│ └── BMSSession.swift GATT-Verbindung, Protokollerkennung, Abfrage
|
||||||
|
├── LiveActivity/
|
||||||
|
│ └── LevelActivityManager.swift Startet/aktualisiert/beendet die Live Activity, drosselt auf 1 Hz
|
||||||
├── Store/
|
├── Store/
|
||||||
│ ├── DeviceStore.swift Geräteliste, Persistenz
|
│ ├── DeviceStore.swift Geräteliste, Persistenz
|
||||||
|
│ ├── DemoData.swift Erfundene Werte für den Demo-Modus
|
||||||
│ └── KeychainStore.swift Victron-Schlüssel
|
│ └── KeychainStore.swift Victron-Schlüssel
|
||||||
├── Watch/
|
├── Watch/
|
||||||
│ └── PhoneWatchLink.swift Sendet den Stand, nimmt Befehle der Uhr an
|
│ └── PhoneWatchLink.swift Sendet den Stand, nimmt Befehle der Uhr an
|
||||||
@@ -436,9 +491,11 @@ VanControl/
|
|||||||
├── DeviceDetailView.swift Alle Werte, Verlauf, Zellspannungen, Diagnose
|
├── DeviceDetailView.swift Alle Werte, Verlauf, Zellspannungen, Diagnose
|
||||||
├── ProfilesView.swift Fahrzeuge anlegen und verwalten
|
├── ProfilesView.swift Fahrzeuge anlegen und verwalten
|
||||||
├── FridgeControls.swift Bedienelemente der Kühlbox
|
├── FridgeControls.swift Bedienelemente der Kühlbox
|
||||||
├── LevelView.swift Libelle und Kalibrierung
|
├── LevelView.swift Libelle, Kalibrierung, Live-Activity-Schalter
|
||||||
|
├── LevelSetupView.swift Einbaulage und Nullpunkt
|
||||||
├── AlignmentAssistantView.swift Ausrichtungs-Assistent fürs Rangieren
|
├── AlignmentAssistantView.swift Ausrichtungs-Assistent fürs Rangieren
|
||||||
├── VehicleTiltView.swift Neigung am Fahrzeug dargestellt
|
├── VehicleTiltView.swift Neigung am Fahrzeug dargestellt
|
||||||
|
├── VictronKeyView.swift Verschlüsselungsschlüssel eintragen
|
||||||
├── SettingsView.swift App-Einstellungen
|
├── SettingsView.swift App-Einstellungen
|
||||||
├── SensorSetupView.swift Assistent für die Einbaulage
|
├── SensorSetupView.swift Assistent für die Einbaulage
|
||||||
└── AddDeviceView.swift Scannen und Einrichten
|
└── AddDeviceView.swift Scannen und Einrichten
|
||||||
@@ -457,8 +514,16 @@ VanControlWatch/ watchOS-App
|
|||||||
├── WatchDeviceDetailView.swift Alle Werte eines Geräts
|
├── WatchDeviceDetailView.swift Alle Werte eines Geräts
|
||||||
└── WatchFridgeControls.swift Kühlbox stellen
|
└── WatchFridgeControls.swift Kühlbox stellen
|
||||||
|
|
||||||
|
VanControlComplication/ Zifferblatt-Komplikation (watchOS)
|
||||||
|
└── LevelComplication.swift Tippt auf die Nivellierung, ohne eigenen Messwert
|
||||||
|
|
||||||
|
VanControlLiveActivity/ Live-Activity-Extension (WidgetKit)
|
||||||
|
├── VanControlLiveActivityBundle.swift Einstieg der Extension
|
||||||
|
└── VanControlLiveActivity.swift Ansicht für Sperrbildschirm, Dynamic Island, CarPlay
|
||||||
|
|
||||||
Config/
|
Config/
|
||||||
└── VanControl-Info.plist Nur der Hintergrundbetrieb für die Uhr
|
├── VanControl-Info.plist Nur der Hintergrundbetrieb für die Uhr
|
||||||
|
└── VanControlComplication-Info.plist Anzeigename der Komplikation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bekannte Grenzen
|
## Bekannte Grenzen
|
||||||
@@ -478,6 +543,9 @@ Config/
|
|||||||
die iPhone-App zu schliessen – dann gehört der Sensor der Uhr.
|
die iPhone-App zu schliessen – dann gehört der Sensor der Uhr.
|
||||||
* **Auf der Uhr muss beim ersten Start Bluetooth erlaubt werden**, sonst bleibt
|
* **Auf der Uhr muss beim ersten Start Bluetooth erlaubt werden**, sonst bleibt
|
||||||
die Nivellierung dort leer.
|
die Nivellierung dort leer.
|
||||||
|
* **Die Live Activity aktualisiert höchstens einmal je Sekunde.** Öfter
|
||||||
|
drosselt iOS lokale Aktualisierungen ohnehin von sich aus, zunehmend
|
||||||
|
stärker – sichtbar vor allem in CarPlay. Siehe [Live Activity](#live-activity).
|
||||||
* **Das Modbus-Registerlayout des neuen Daly-Protokolls variiert zwischen
|
* **Das Modbus-Registerlayout des neuen Daly-Protokolls variiert zwischen
|
||||||
Firmwareständen.** Das klassische `A5`-Protokoll und das JBD-Protokoll sind
|
Firmwareständen.** Das klassische `A5`-Protokoll und das JBD-Protokoll sind
|
||||||
gut dokumentiert; falls dein BMS Modbus spricht und Werte unplausibel
|
gut dokumentiert; falls dein BMS Modbus spricht und Werte unplausibel
|
||||||
|
|||||||
@@ -3,18 +3,30 @@ import Foundation
|
|||||||
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
|
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
|
||||||
/// Fahrzeug und ohne Bluetooth prüfen lassen.
|
/// Fahrzeug und ohne Bluetooth prüfen lassen.
|
||||||
///
|
///
|
||||||
/// Nur in Debug-Builds und nur, wenn beim Start `CAMPER_DEMO=1` gesetzt ist:
|
/// Nur in Debug-Builds, auf zwei Wegen einzuschalten:
|
||||||
|
///
|
||||||
|
/// * Umgebungsvariable `CAMPER_DEMO=1` beim Start – praktisch im Simulator:
|
||||||
///
|
///
|
||||||
/// xcrun simctl launch --terminate-running-process \
|
/// xcrun simctl launch --terminate-running-process \
|
||||||
/// booted de.fritob.VanControl
|
/// booted de.fritob.VanControl
|
||||||
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
|
/// # 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.
|
/// Im normalen Betrieb wird hiervon nichts ausgeführt.
|
||||||
enum DemoData {
|
enum DemoData {
|
||||||
|
|
||||||
|
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
|
||||||
|
static let enabledKey = "demoModeEnabled"
|
||||||
|
|
||||||
static var isEnabled: Bool {
|
static var isEnabled: Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
return ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1"
|
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
|
||||||
|
return UserDefaults.standard.bool(forKey: enabledKey)
|
||||||
#else
|
#else
|
||||||
return false
|
return false
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ struct DeviceCard: View {
|
|||||||
|
|
||||||
if isShowingLastSettings, let snapshot {
|
if isShowingLastSettings, let snapshot {
|
||||||
lastSettings(for: snapshot)
|
lastSettings(for: snapshot)
|
||||||
|
} else if let snapshot, snapshot.primaryMetric != nil, device.role == .leveling {
|
||||||
|
levelReadout(for: snapshot)
|
||||||
} else if let snapshot, let primary = snapshot.primaryMetric {
|
} else if let snapshot, let primary = snapshot.primaryMetric {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||||
Text(primary.formatted)
|
Text(primary.formatted)
|
||||||
@@ -115,6 +117,21 @@ struct DeviceCard: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Für die Nivellierung: Front/Heck und links/rechts nebeneinander, groß,
|
||||||
|
/// ohne "Längsneigung"/"Querneigung"-Beschriftung – die Richtung steht ja
|
||||||
|
/// schon im vorangestellten Buchstaben.
|
||||||
|
private func levelReadout(for snapshot: DeviceSnapshot) -> some View {
|
||||||
|
let pitch = snapshot.metrics.first(where: { $0.key == "pitch" })?.value
|
||||||
|
let roll = snapshot.metrics.first(where: { $0.key == "roll" })?.value
|
||||||
|
return HStack(spacing: 24) {
|
||||||
|
Text(LevelDirectionFormatting.pitchTile(pitch))
|
||||||
|
Text(LevelDirectionFormatting.rollTile(roll))
|
||||||
|
}
|
||||||
|
.font(.system(size: 36, weight: .semibold, design: .rounded))
|
||||||
|
.contentTransition(.numericText())
|
||||||
|
.foregroundStyle(snapshot.isStale ? .secondary : .primary)
|
||||||
|
}
|
||||||
|
|
||||||
private func secondaryValues(for snapshot: DeviceSnapshot) -> some View {
|
private func secondaryValues(for snapshot: DeviceSnapshot) -> some View {
|
||||||
let others = snapshot.metrics
|
let others = snapshot.metrics
|
||||||
.filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil }
|
.filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil }
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ struct LevelSetupView: View {
|
|||||||
store.devices.first { $0.id == device.id } ?? device
|
store.devices.first { $0.id == device.id } ?? device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let graphicColumns = [GridItem(.adaptive(minimum: 76), spacing: 12)]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
@@ -80,6 +82,44 @@ struct LevelSetupView: View {
|
|||||||
} footer: {
|
} footer: {
|
||||||
Text(calibrationHint)
|
Text(calibrationHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
LazyVGrid(columns: graphicColumns, spacing: 12) {
|
||||||
|
ForEach(VehicleGraphicStyle.allCases) { style in
|
||||||
|
Button {
|
||||||
|
var updated = currentDevice
|
||||||
|
updated.vehicleGraphicStyle = style
|
||||||
|
store.update(updated)
|
||||||
|
} label: {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Image(style.sideImageName)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.padding(10)
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.background(currentDevice.vehicleGraphicStyle == style
|
||||||
|
? Color.accentColor.opacity(0.18)
|
||||||
|
: Color.secondary.opacity(0.08),
|
||||||
|
in: .rect(cornerRadius: 12))
|
||||||
|
.foregroundStyle(currentDevice.vehicleGraphicStyle == style
|
||||||
|
? Color.accentColor : Color.primary)
|
||||||
|
Text(style.title)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(currentDevice.vehicleGraphicStyle == style
|
||||||
|
? Color.accentColor : Color.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.accessibilityLabel(style.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
} header: {
|
||||||
|
Text("Fahrzeuggrafik")
|
||||||
|
} footer: {
|
||||||
|
Text("Wird in der Fahrzeug-Darstellung des Neigungsmessers verwendet. "
|
||||||
|
+ "Weitere Grafiken folgen.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Neigungsmesser einrichten")
|
.navigationTitle("Neigungsmesser einrichten")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
|||||||
@@ -4,12 +4,28 @@ import SwiftUI
|
|||||||
/// betrifft, steht bei diesem Gerät.
|
/// betrifft, steht bei diesem Gerät.
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
||||||
|
#if DEBUG
|
||||||
|
@AppStorage(DemoData.enabledKey) private var demoModeEnabled = false
|
||||||
|
#endif
|
||||||
@Environment(PhoneWatchLink.self) private var watch
|
@Environment(PhoneWatchLink.self) private var watch
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
|
#if DEBUG
|
||||||
|
Section {
|
||||||
|
Toggle("Demo-Modus", isOn: $demoModeEnabled)
|
||||||
|
} header: {
|
||||||
|
Text("Entwicklung")
|
||||||
|
} footer: {
|
||||||
|
Text("Füllt die App mit erfundenen Fahrzeugen und Messwerten, um "
|
||||||
|
+ "Ansichten ohne echte Sensoren zu prüfen. Wirkt erst nach "
|
||||||
|
+ "einem Neustart der App – einmal beenden (im App-Umschalter "
|
||||||
|
+ "nach oben wischen) und wieder öffnen.")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
||||||
} header: {
|
} header: {
|
||||||
|
|||||||
Reference in New Issue
Block a user