Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae12d8ede4 | ||
|
|
b1ffbe324a | ||
|
|
8401f62d91 | ||
|
|
c45cf36920 | ||
|
|
47ead9b006 | ||
|
|
b23e96615e | ||
|
|
0b3bf5e815 | ||
|
|
8d58ea6e49 | ||
|
|
141e321a6f | ||
|
|
35f4e6fbcb |
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>bluetooth-central</string>
|
||||||
|
</array>
|
||||||
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
|
<string>Damit die Nivellierung auf dem CarPlay-Display nur im Stand angezeigt wird.</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>Damit die Nivellierung auf dem CarPlay-Display nur im Stand angezeigt wird.</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<true/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>CarPlay</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>CarPlaySceneDelegate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.developer.carplay-maps</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -430,16 +430,18 @@ eingerichtet wurden, wandern beim Update automatisch ins erste Profil.
|
|||||||
## Ohne Fahrzeug ansehen
|
## Ohne Fahrzeug ansehen
|
||||||
|
|
||||||
Ein Demo-Modus füllt die App mit erfundenen Fahrzeugen und Messwerten, damit
|
Ein Demo-Modus füllt die App mit erfundenen Fahrzeugen und Messwerten, damit
|
||||||
sich die Ansichten ohne Bluetooth prüfen lassen. Nur in Debug-Builds, auf zwei
|
sich die Ansichten ohne Bluetooth prüfen lassen – auch ohne die Hardware zur
|
||||||
Wegen einzuschalten:
|
Hand zu haben. Auf zwei Wegen einzuschalten:
|
||||||
|
|
||||||
* **Schalter in den Einstellungen** (*Einstellungen → Entwicklung →
|
* **Schalter in den Einstellungen** (*Einstellungen → Entwicklung →
|
||||||
Demo-Modus*) – der Weg für einen lokal installierten Build ohne
|
Demo-Modus*) – in jeder Build-Konfiguration verfügbar, auch in TestFlight-
|
||||||
Xcode-Verbindung, etwa auf dem eigenen iPhone. Wirkt erst nach einem
|
und App-Store-Builds. Der Weg für jeden lokal installierten Build ohne
|
||||||
Neustart der App (im App-Umschalter nach oben wischen, dann neu öffnen),
|
Xcode-Verbindung. Wirkt erst nach einem Neustart der App (im
|
||||||
weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start lesen.
|
App-Umschalter nach oben wischen, dann neu öffnen), weil `DeviceStore` und
|
||||||
* **Umgebungsvariable** `CAMPER_DEMO=1` – in Xcode unter *Product → Scheme →
|
`BluetoothManager` den Stand nur beim Start lesen.
|
||||||
Edit Scheme → Run → Arguments*, oder im Simulator direkt:
|
* **Umgebungsvariable** `CAMPER_DEMO=1` – nur in Debug-Builds, in Xcode unter
|
||||||
|
*Product → Scheme → Edit Scheme → Run → Arguments*, oder im Simulator
|
||||||
|
direkt:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xcrun simctl launch --terminate-running-process booted <deine-bundle-id>
|
xcrun simctl launch --terminate-running-process booted <deine-bundle-id>
|
||||||
|
|||||||
@@ -65,6 +65,25 @@ struct LevelState: Equatable, Codable, Sendable {
|
|||||||
return parts.joined(separator: ", ")
|
return parts.joined(separator: ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dasselbe wie ``instruction``, aber in kurzen Zeilen statt einem Satz –
|
||||||
|
/// für schmale Anzeigen wie das CarPlay-Display, wo "Heck steht höher,
|
||||||
|
/// rechts steht höher" nicht in eine lesbare Zeile passt.
|
||||||
|
///
|
||||||
|
/// Die Vorzeichen müssen zu ``instruction`` passen; beide stehen deshalb
|
||||||
|
/// nebeneinander.
|
||||||
|
var shortInstructions: [String] {
|
||||||
|
guard let pitch, let roll else { return [] }
|
||||||
|
if isLevel { return ["Steht eben"] }
|
||||||
|
var parts: [String] = []
|
||||||
|
if abs(pitch) > Self.levelTolerance {
|
||||||
|
parts.append(pitch > 0 ? "Heck höher" : "Front höher")
|
||||||
|
}
|
||||||
|
if abs(roll) > Self.levelTolerance {
|
||||||
|
parts.append(roll > 0 ? "Rechts höher" : "Links höher")
|
||||||
|
}
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
func snapshot(deviceID: UUID, rssi: Int?) -> DeviceSnapshot {
|
func snapshot(deviceID: UUID, rssi: Int?) -> DeviceSnapshot {
|
||||||
var snapshot = DeviceSnapshot(deviceID: deviceID, timestamp: Date(), rssi: rssi)
|
var snapshot = DeviceSnapshot(deviceID: deviceID, timestamp: Date(), rssi: rssi)
|
||||||
snapshot.metrics = [
|
snapshot.metrics = [
|
||||||
|
|||||||
@@ -26,11 +26,10 @@ enum VehicleGraphicStyle: String, CaseIterable, Identifiable, Codable, Sendable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Für California liegt noch keine eigene Heckansicht vor, deshalb
|
|
||||||
/// vorerst die von Vanster.
|
|
||||||
var rearImageName: String {
|
var rearImageName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .vanster, .california: return "VansterRear"
|
case .vanster: return "VansterRear"
|
||||||
|
case .california: return "CaliforniaRear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ enum VehicleGraphicStyle: String, CaseIterable, Identifiable, Codable, Sendable
|
|||||||
var sideAspect: Double {
|
var sideAspect: Double {
|
||||||
switch self {
|
switch self {
|
||||||
case .vanster: return VehicleTilt.sideAspect
|
case .vanster: return VehicleTilt.sideAspect
|
||||||
case .california: return 1
|
case .california: return 1212.0 / 790.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ struct WatchDevice: Codable, Equatable, Identifiable, Sendable {
|
|||||||
/// den Neigungsmesser selbst an und braucht die Zuordnung deshalb auch –
|
/// den Neigungsmesser selbst an und braucht die Zuordnung deshalb auch –
|
||||||
/// eingestellt wird sie weiterhin nur am iPhone.
|
/// eingestellt wird sie weiterhin nur am iPhone.
|
||||||
var orientation: SensorOrientation?
|
var orientation: SensorOrientation?
|
||||||
|
/// Ebenfalls nur beim Neigungsmesser: welche Fahrzeuggrafik die
|
||||||
|
/// Fahrzeug-Ansicht zeigt. Ausgewählt wird sie nur am iPhone, die Uhr
|
||||||
|
/// übernimmt nur die Anzeige.
|
||||||
|
var vehicleGraphicStyle: VehicleGraphicStyle?
|
||||||
/// Nur bei der Kühlbox belegt.
|
/// Nur bei der Kühlbox belegt.
|
||||||
var fridge: WatchFridge?
|
var fridge: WatchFridge?
|
||||||
|
|
||||||
|
|||||||
@@ -1190,13 +1190,14 @@ let watchPayload = WatchPayload(
|
|||||||
devices: [
|
devices: [
|
||||||
WatchDevice(id: watchLevelID, name: "Nivellierung", role: .leveling,
|
WatchDevice(id: watchLevelID, name: "Nivellierung", role: .leveling,
|
||||||
link: .live, snapshot: watchLevelSnapshot,
|
link: .live, snapshot: watchLevelSnapshot,
|
||||||
level: watchLevel, orientation: watchOrientation, fridge: nil),
|
level: watchLevel, orientation: watchOrientation,
|
||||||
|
vehicleGraphicStyle: .vanster, fridge: nil),
|
||||||
WatchDevice(id: watchBoosterID, name: "Ladebooster", role: .chargeBooster,
|
WatchDevice(id: watchBoosterID, name: "Ladebooster", role: .chargeBooster,
|
||||||
link: .failed("Nicht gefunden"), snapshot: boosterSnapshot,
|
link: .failed("Nicht gefunden"), snapshot: boosterSnapshot,
|
||||||
level: nil, orientation: nil, fridge: nil),
|
level: nil, orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
|
||||||
WatchDevice(id: watchFridgeID, name: "Kühlbox", role: .fridge,
|
WatchDevice(id: watchFridgeID, name: "Kühlbox", role: .fridge,
|
||||||
link: .live, snapshot: nil, level: nil,
|
link: .live, snapshot: nil, level: nil,
|
||||||
orientation: nil, fridge: watchFridge),
|
orientation: nil, vehicleGraphicStyle: nil, fridge: watchFridge),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Der ganze Weg: verpacken, als WatchConnectivity-Wörterbuch lesen, vergleichen.
|
// Der ganze Weg: verpacken, als WatchConnectivity-Wörterbuch lesen, vergleichen.
|
||||||
|
|||||||
@@ -38,5 +38,22 @@ sips --resampleHeight 600 heckansicht.png --out /tmp/rear.png
|
|||||||
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
|
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
|
||||||
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
|
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
|
||||||
transparenten Hintergrund, geht die Quelldatei direkt ins Skript. Liegt sie
|
transparenten Hintergrund, geht die Quelldatei direkt ins Skript. Liegt sie
|
||||||
auf weißem Grund, muss der Rand vorher weg – sonst wird die ganze Fläche
|
auf weißem Grund, muss der Rand vorher weg – siehe `remove-white-background.swift`.
|
||||||
mitgefärbt.
|
|
||||||
|
## remove-white-background.swift
|
||||||
|
|
||||||
|
Vorstufe zu `make-vehicle-art.swift`, wenn die Vorlage auf weißem statt
|
||||||
|
transparentem Grund liegt (kein Alphakanal). Ohne diesen Schritt hielte
|
||||||
|
`make-vehicle-art` die ganze Fläche für Zeichnung und färbte den Hintergrund
|
||||||
|
mit ein.
|
||||||
|
|
||||||
|
Reines Weiß wird voll durchsichtig, alles andere bleibt stehen; ein weicher
|
||||||
|
Übergang statt einer harten Schwelle vermeidet gezackte Kanten an
|
||||||
|
kantengeglätteten Linien.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
swiftc -O -o /tmp/remove-white-background Tools/remove-white-background.swift
|
||||||
|
swiftc -O -o /tmp/make-vehicle-art Tools/make-vehicle-art.swift
|
||||||
|
/tmp/remove-white-background seitenansicht.png /tmp/seite-transparent.png
|
||||||
|
/tmp/make-vehicle-art /tmp/seite-transparent.png VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import AppKit
|
||||||
|
import CoreGraphics
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// Vorlagen kommen manchmal auf weißem statt transparentem Grund. Ohne
|
||||||
|
// Alphakanal hielte `make-vehicle-art.swift` die ganze Fläche für Zeichnung
|
||||||
|
// und färbte den Hintergrund mit ein. Dieses Werkzeug macht daraus einen
|
||||||
|
// echten Alphakanal: reines Weiß wird voll durchsichtig, alles andere bleibt
|
||||||
|
// stehen. Ein weicher Übergang statt einer harten Schwelle verhindert
|
||||||
|
// gezackte Kanten an den (leicht kantengeglätteten) Linien.
|
||||||
|
|
||||||
|
let arguments = CommandLine.arguments
|
||||||
|
guard arguments.count >= 3,
|
||||||
|
let source = NSImage(contentsOfFile: arguments[1]),
|
||||||
|
let image = source.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||||
|
print("Quelle nicht lesbar"); exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = image.width, height = image.height
|
||||||
|
let bytesPerRow = width * 4
|
||||||
|
var pixels = [UInt8](repeating: 0, count: bytesPerRow * height)
|
||||||
|
guard let context = CGContext(data: &pixels, width: width, height: height,
|
||||||
|
bitsPerComponent: 8, bytesPerRow: bytesPerRow,
|
||||||
|
space: CGColorSpaceCreateDeviceRGB(),
|
||||||
|
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
|
||||||
|
print("Puffer nicht anlegbar"); exit(1)
|
||||||
|
}
|
||||||
|
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
||||||
|
|
||||||
|
/// Unterhalb davon gilt ein Pixel als reiner Hintergrund, oberhalb als
|
||||||
|
/// sicher Zeichnung. Dazwischen wird linear zwischen 0 und voller Deckkraft
|
||||||
|
/// überblendet, damit Kanten nicht ausgefranst wirken.
|
||||||
|
let whiteFloor = 230.0
|
||||||
|
let whiteCeiling = 250.0
|
||||||
|
|
||||||
|
for index in stride(from: 0, to: pixels.count, by: 4) {
|
||||||
|
// Quelle ist opak (kein Alphakanal), also stehen hier die Rohfarben.
|
||||||
|
let r = Double(pixels[index])
|
||||||
|
let g = Double(pixels[index + 1])
|
||||||
|
let b = Double(pixels[index + 2])
|
||||||
|
let whiteness = min(r, g, b)
|
||||||
|
|
||||||
|
let t = min(1, max(0, (whiteness - whiteFloor) / (whiteCeiling - whiteFloor)))
|
||||||
|
let alpha = 1 - t
|
||||||
|
guard alpha > 0.004 else {
|
||||||
|
pixels[index] = 0; pixels[index + 1] = 0; pixels[index + 2] = 0; pixels[index + 3] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Premultipliziert ablegen.
|
||||||
|
pixels[index] = UInt8(r * alpha)
|
||||||
|
pixels[index + 1] = UInt8(g * alpha)
|
||||||
|
pixels[index + 2] = UInt8(b * alpha)
|
||||||
|
pixels[index + 3] = UInt8(alpha * 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let result = context.makeImage() else { print("Bild leer"); exit(1) }
|
||||||
|
let rep = NSBitmapImageRep(cgImage: result)
|
||||||
|
guard let png = rep.representation(using: .png, properties: [:]) else {
|
||||||
|
print("PNG nicht erzeugbar"); exit(1)
|
||||||
|
}
|
||||||
|
try png.write(to: URL(fileURLWithPath: arguments[2]))
|
||||||
|
print("\(arguments[2]): fertig")
|
||||||
@@ -496,16 +496,20 @@
|
|||||||
AA0000000000000000000014 /* Debug */ = {
|
AA0000000000000000000014 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
||||||
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
||||||
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -516,7 +520,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -528,16 +532,20 @@
|
|||||||
AA0000000000000000000015 /* Release */ = {
|
AA0000000000000000000015 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
||||||
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
||||||
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -548,7 +556,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -563,7 +571,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -574,7 +582,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -595,7 +603,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -606,7 +614,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -625,7 +633,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
||||||
@@ -635,7 +643,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -654,7 +662,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
||||||
@@ -664,7 +672,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -685,7 +693,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
||||||
@@ -696,7 +704,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -714,7 +722,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
||||||
@@ -725,7 +733,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -737,6 +745,177 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
AA0000000000000000000090 /* CarPlay */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG CARPLAY_DEMO $(inherited)";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = CarPlay;
|
||||||
|
};
|
||||||
|
AA0000000000000000000091 /* CarPlay */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Config/VanControlCarPlay.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphonesimulator*]" = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=watchsimulator*]" = "-";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
|
DEVELOPMENT_TEAM = P8J6283TT5;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = "Config/VanControlCarPlay-Info.plist";
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = "VanControl CarPlay";
|
||||||
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
||||||
|
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
||||||
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO;
|
||||||
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 0.1;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControlCarPlay;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG CARPLAY_DEMO $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = CarPlay;
|
||||||
|
};
|
||||||
|
AA0000000000000000000092 /* CarPlay */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphonesimulator*]" = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=watchsimulator*]" = "-";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
|
DEVELOPMENT_TEAM = P8J6283TT5;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = "VanControl CarPlay";
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen des Neigungsmessers direkt an der Uhr, ohne Umweg ueber das iPhone.";
|
||||||
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = de.s0.fototeddy.VanControlCarPlay;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 0.1;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControlCarPlay.watchkitapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTED_PLATFORMS = "watchos watchsimulator";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = CarPlay;
|
||||||
|
};
|
||||||
|
AA0000000000000000000093 /* CarPlay */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphonesimulator*]" = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=watchsimulator*]" = "-";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
|
DEVELOPMENT_TEAM = P8J6283TT5;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Nivellierung;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 0.1;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControlCarPlay.watchkitapp.levelwidget;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTED_PLATFORMS = "watchos watchsimulator";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = CarPlay;
|
||||||
|
};
|
||||||
|
AA0000000000000000000094 /* CarPlay */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphonesimulator*]" = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=watchsimulator*]" = "-";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
|
DEVELOPMENT_TEAM = P8J6283TT5;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Live Activity";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 0.1;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControlCarPlay.VanControlLiveActivity;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = CarPlay;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@@ -744,6 +923,7 @@
|
|||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
AA0000000000000000000012 /* Debug */,
|
AA0000000000000000000012 /* Debug */,
|
||||||
|
AA0000000000000000000090 /* CarPlay */,
|
||||||
AA0000000000000000000013 /* Release */,
|
AA0000000000000000000013 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -753,6 +933,7 @@
|
|||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
AA0000000000000000000014 /* Debug */,
|
AA0000000000000000000014 /* Debug */,
|
||||||
|
AA0000000000000000000091 /* CarPlay */,
|
||||||
AA0000000000000000000015 /* Release */,
|
AA0000000000000000000015 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -762,6 +943,7 @@
|
|||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
AA0000000000000000000023 /* Debug */,
|
AA0000000000000000000023 /* Debug */,
|
||||||
|
AA0000000000000000000092 /* CarPlay */,
|
||||||
AA0000000000000000000024 /* Release */,
|
AA0000000000000000000024 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -771,6 +953,7 @@
|
|||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
AA0000000000000000000038 /* Debug */,
|
AA0000000000000000000038 /* Debug */,
|
||||||
|
AA0000000000000000000093 /* CarPlay */,
|
||||||
AA0000000000000000000039 /* Release */,
|
AA0000000000000000000039 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -780,6 +963,7 @@
|
|||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
AA0000000000000000000066 /* Debug */,
|
AA0000000000000000000066 /* Debug */,
|
||||||
|
AA0000000000000000000094 /* CarPlay */,
|
||||||
AA0000000000000000000067 /* Release */,
|
AA0000000000000000000067 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2660"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "AA0000000000000000000006"
|
||||||
|
BuildableName = "VanControl.app"
|
||||||
|
BlueprintName = "VanControl"
|
||||||
|
ReferencedContainer = "container:VanControl.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "CarPlay"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "CarPlay"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "AA0000000000000000000006"
|
||||||
|
BuildableName = "VanControl.app"
|
||||||
|
BlueprintName = "VanControl"
|
||||||
|
ReferencedContainer = "container:VanControl.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "CARPLAY_DEMO_PITCH"
|
||||||
|
value = "1.8"
|
||||||
|
isEnabled = "NO">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "CARPLAY_DEMO_ROLL"
|
||||||
|
value = "-0.9"
|
||||||
|
isEnabled = "NO">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
</EnvironmentVariables>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "CarPlay"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "AA0000000000000000000006"
|
||||||
|
BuildableName = "VanControl.app"
|
||||||
|
BlueprintName = "VanControl"
|
||||||
|
ReferencedContainer = "container:VanControl.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "CarPlay">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "CarPlay"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIcon-App1-Testflight-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIcon-App1-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIcon-App2-Testflight-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIcon-App2-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 326 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "CaliforniaRear.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 },
|
||||||
|
"properties" : { "template-rendering-intent" : "template" }
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 311 KiB |
@@ -0,0 +1,91 @@
|
|||||||
|
#if CARPLAY_DEMO
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Die Libelle auf dem Fahrzeugdisplay.
|
||||||
|
///
|
||||||
|
/// Quer geteilt: links die Blase, rechts in Worten, was zu tun ist. Beim
|
||||||
|
/// Rangieren wird abwechselnd auf Display und Umfeld geschaut – deshalb steht
|
||||||
|
/// die Aussage in einem Satz da, statt sich aus zwei Zahlen ergeben zu müssen.
|
||||||
|
///
|
||||||
|
/// Die Groessen sind auf die 400 × 240 **Punkte** ausgelegt, die CarPlay
|
||||||
|
/// liefert – nicht auf die 800 × 480 Pixel. Das ist die Falle: In Pixeln
|
||||||
|
/// gedachte Schriftgroessen sind auf dem Fahrzeugdisplay doppelt zu gross und
|
||||||
|
/// schneiden den Text ab.
|
||||||
|
struct CarPlayLevelScreen: View {
|
||||||
|
@Environment(CarPlayLevelSource.self) private var source
|
||||||
|
|
||||||
|
private var state: LevelState {
|
||||||
|
LevelState(pitch: source.pitch, roll: source.roll)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black.ignoresSafeArea()
|
||||||
|
|
||||||
|
if CarPlaySpeedGate.shared.isStationary {
|
||||||
|
levelling
|
||||||
|
} else {
|
||||||
|
driving
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear { CarPlaySpeedGate.shared.start() }
|
||||||
|
.onDisappear { CarPlaySpeedGate.shared.stop() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var levelling: some View {
|
||||||
|
HStack(spacing: 18) {
|
||||||
|
LevelBubble(pitch: source.pitch, roll: source.roll)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
let lines = state.shortInstructions
|
||||||
|
ForEach(lines.isEmpty ? ["Keine Verbindung"] : lines, id: \.self) { line in
|
||||||
|
Text(line)
|
||||||
|
.font(.system(size: 20, weight: .semibold, design: .rounded))
|
||||||
|
.foregroundStyle(state.isLevel ? .green : .white)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
tile("Längs", LevelDirectionFormatting.pitchTile(source.pitch))
|
||||||
|
tile("Quer", LevelDirectionFormatting.rollTile(source.roll))
|
||||||
|
}
|
||||||
|
|
||||||
|
if source.isSimulated {
|
||||||
|
// Ohne diesen Hinweis wäre ein Screenshot aus dem Simulator
|
||||||
|
// eine Behauptung über Messwerte, die es nicht gab.
|
||||||
|
Text("Simulierte Werte")
|
||||||
|
.font(.system(size: 11, weight: .medium))
|
||||||
|
.foregroundStyle(.orange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
.padding(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var driving: some View {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Image(systemName: "car.fill")
|
||||||
|
.font(.system(size: 30))
|
||||||
|
Text("Nivellierung nur im Stand")
|
||||||
|
.font(.system(size: 20, weight: .semibold, design: .rounded))
|
||||||
|
}
|
||||||
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tile(_ label: String, _ value: String) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 1) {
|
||||||
|
Text(label)
|
||||||
|
.font(.system(size: 11, weight: .medium))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Text(value)
|
||||||
|
.font(.system(size: 22, weight: .semibold, design: .rounded))
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#if CARPLAY_DEMO
|
||||||
|
import Foundation
|
||||||
|
import Observation
|
||||||
|
|
||||||
|
/// Woher die CarPlay-Libelle ihre Werte bekommt.
|
||||||
|
///
|
||||||
|
/// Im Simulator gibt es kein Bluetooth und damit keinen Neigungsmesser. Für
|
||||||
|
/// die Demo läuft deshalb ein simulierter Verlauf: ein Fahrzeug, das langsam
|
||||||
|
/// auf die Keile rollt, gerade wird und wieder abrutscht. Das reicht, um die
|
||||||
|
/// Anzeige zu beurteilen – und für Screenshots, die zeigen, was die Funktion
|
||||||
|
/// tut.
|
||||||
|
///
|
||||||
|
/// Für gezielte Aufnahmen lassen sich feste Werte über die Umgebung setzen:
|
||||||
|
/// `CARPLAY_DEMO_PITCH` und `CARPLAY_DEMO_ROLL` (in Grad). Dann steht die
|
||||||
|
/// Blase still und der Screenshot ist reproduzierbar.
|
||||||
|
///
|
||||||
|
/// Am Fahrzeug speist stattdessen der `BluetoothManager` über ``apply(_:)``
|
||||||
|
/// ein; die Demo-Schleife läuft dann nicht.
|
||||||
|
@Observable
|
||||||
|
final class CarPlayLevelSource {
|
||||||
|
static let shared = CarPlayLevelSource()
|
||||||
|
|
||||||
|
private(set) var pitch: Double?
|
||||||
|
private(set) var roll: Double?
|
||||||
|
|
||||||
|
/// Ob die Werte simuliert sind. Die Anzeige weist darauf hin – ein
|
||||||
|
/// Screenshot ohne diesen Hinweis wäre gegenüber Apple nicht ehrlich.
|
||||||
|
private(set) var isSimulated = false
|
||||||
|
|
||||||
|
@ObservationIgnored private var timer: Timer?
|
||||||
|
@ObservationIgnored private var phase: Double = 0
|
||||||
|
@ObservationIgnored private lazy var isSequenced =
|
||||||
|
ProcessInfo.processInfo.environment["CARPLAY_DEMO_SEQUENCE"] != nil
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
/// Zehn Aktualisierungen pro Sekunde – schnell genug, dass die Blase der
|
||||||
|
/// Bewegung des Fahrzeugs folgt statt ihr nachzulaufen.
|
||||||
|
private static let updateInterval: TimeInterval = 0.1
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
guard timer == nil else { return }
|
||||||
|
|
||||||
|
let environment = ProcessInfo.processInfo.environment
|
||||||
|
if let fixedPitch = environment["CARPLAY_DEMO_PITCH"].flatMap(Double.init),
|
||||||
|
let fixedRoll = environment["CARPLAY_DEMO_ROLL"].flatMap(Double.init) {
|
||||||
|
pitch = fixedPitch
|
||||||
|
roll = fixedRoll
|
||||||
|
isSimulated = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSimulated = true
|
||||||
|
let timer = Timer.scheduledTimer(withTimeInterval: Self.updateInterval, repeats: true) { [weak self] _ in
|
||||||
|
self?.step()
|
||||||
|
}
|
||||||
|
RunLoop.main.add(timer, forMode: .common)
|
||||||
|
self.timer = timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Echte Messwerte vom Neigungsmesser. Sobald die kommen, ist nichts mehr
|
||||||
|
/// simuliert.
|
||||||
|
func apply(_ state: LevelState) {
|
||||||
|
stop()
|
||||||
|
isSimulated = false
|
||||||
|
pitch = state.pitch
|
||||||
|
roll = state.roll
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Feste Zustände im Wechsel, je sieben Sekunden – für Screenshots, die
|
||||||
|
/// beide Fälle zuverlässig zeigen statt auf den richtigen Moment einer
|
||||||
|
/// Schwingung zu warten.
|
||||||
|
private static let sequence: [(pitch: Double, roll: Double)] = [
|
||||||
|
(1.8, -0.9), // deutlich schief
|
||||||
|
(0.2, -0.1), // ausgerichtet
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Ein Fahrzeug, das sich über gut zwanzig Sekunden einpendelt und wieder
|
||||||
|
/// abweicht. Zwei unterschiedlich schnelle Schwingungen, damit Längs- und
|
||||||
|
/// Querneigung nicht im Gleichtakt laufen – das sähe mechanisch aus.
|
||||||
|
private func step() {
|
||||||
|
phase += Self.updateInterval
|
||||||
|
|
||||||
|
if isSequenced {
|
||||||
|
// Dritte Phase: die Sperre waehrend der Fahrt.
|
||||||
|
let step = Int(phase / 7) % (Self.sequence.count + 1)
|
||||||
|
if step == Self.sequence.count {
|
||||||
|
CarPlaySpeedGate.shared.setDemoDriving(true)
|
||||||
|
} else {
|
||||||
|
CarPlaySpeedGate.shared.setDemoDriving(false)
|
||||||
|
(pitch, roll) = Self.sequence[step]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pitch = 3.2 * sin(phase / 3.4)
|
||||||
|
roll = 2.1 * sin(phase / 2.1 + 0.7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Zeigt die CarPlay-Ansicht im iPhone-Simulator, in den Maßen, die CarPlay
|
||||||
|
/// tatsächlich liefert (400 × 240 Punkte, also 800 × 480 Pixel).
|
||||||
|
///
|
||||||
|
/// Nur zum Beurteilen des Layouts, solange der CarPlay-Simulator selbst nicht
|
||||||
|
/// zur Verfügung steht. Ohne `CARPLAY_DEMO_PREVIEW` in der Umgebung passiert
|
||||||
|
/// nichts, und ohne das `CARPLAY_DEMO`-Flag existiert der Code gar nicht.
|
||||||
|
extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func carPlayPreviewOverlay() -> some View {
|
||||||
|
#if CARPLAY_DEMO
|
||||||
|
if ProcessInfo.processInfo.environment["CARPLAY_DEMO_PREVIEW"] != nil {
|
||||||
|
CarPlayPreviewHost()
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
self
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CARPLAY_DEMO
|
||||||
|
struct CarPlayPreviewHost: View {
|
||||||
|
var body: some View {
|
||||||
|
// Ohne Beiwerk und exakt zentriert: so laesst sich der Screenshot
|
||||||
|
// verlustfrei auf den CarPlay-Bereich zuschneiden.
|
||||||
|
VStack {
|
||||||
|
CarPlayLevelScreen()
|
||||||
|
.environment(CarPlayLevelSource.shared)
|
||||||
|
.frame(width: 400, height: 240)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.background(Color.black)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.onAppear { CarPlayLevelSource.shared.start() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#if CARPLAY_DEMO
|
||||||
|
import CarPlay
|
||||||
|
import OSLog
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Einstiegspunkt der CarPlay-Szene.
|
||||||
|
///
|
||||||
|
/// Mit dem Navigations-Entitlement liefert CarPlay ein eigenes `CPWindow`, in
|
||||||
|
/// das frei gezeichnet werden darf – anders als bei den Template-Kategorien,
|
||||||
|
/// die nur vorgegebene Bausteine erlauben und höchstens alle zehn Sekunden
|
||||||
|
/// aktualisieren. Genau deshalb hängt hier die Libelle drin: Beim Rangieren
|
||||||
|
/// muss sie der Bewegung des Fahrzeugs folgen, nicht ihr hinterherlaufen.
|
||||||
|
///
|
||||||
|
/// Das `CPMapTemplate` darüber ist nur die Bedienleiste. Es bleibt bewusst
|
||||||
|
/// leer: Auf dem Stellplatz gibt es nichts zu tippen, es gibt nur etwas
|
||||||
|
/// abzulesen.
|
||||||
|
@objc(CarPlaySceneDelegate)
|
||||||
|
final class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate, CPMapTemplateDelegate {
|
||||||
|
private var interfaceController: CPInterfaceController?
|
||||||
|
|
||||||
|
/// Ohne eigenes Logging ist beim Debuggen nicht zu unterscheiden, ob die
|
||||||
|
/// Szene gar nicht verbindet oder ob CarPlay das Template ablehnt.
|
||||||
|
private static let log = Logger(subsystem: "de.s0.fototeddy.VanControl", category: "CarPlay")
|
||||||
|
|
||||||
|
func templateApplicationScene(
|
||||||
|
_ templateApplicationScene: CPTemplateApplicationScene,
|
||||||
|
didConnect interfaceController: CPInterfaceController,
|
||||||
|
to window: CPWindow
|
||||||
|
) {
|
||||||
|
Self.log.notice("didConnect: Szene verbunden, Fenster \(NSCoder.string(for: window.bounds.size), privacy: .public)")
|
||||||
|
self.interfaceController = interfaceController
|
||||||
|
|
||||||
|
let root = CarPlayLevelScreen()
|
||||||
|
.environment(CarPlayLevelSource.shared)
|
||||||
|
let host = UIHostingController(rootView: root)
|
||||||
|
host.view.backgroundColor = .black
|
||||||
|
window.rootViewController = host
|
||||||
|
window.makeKeyAndVisible()
|
||||||
|
|
||||||
|
CarPlayLevelSource.shared.start()
|
||||||
|
|
||||||
|
// Bewusst nackt konfiguriert: iOS 26 laesst den CarPlayTemplateUIHost
|
||||||
|
// beim Aufbau der Navigationsleiste ueber den Share-Button stolpern
|
||||||
|
// (`_updateShareButtonVisibility` -> unrecognized selector), sobald die
|
||||||
|
// Leiste erzwungen wird. Ohne Buttons und mit der Standard-Automatik
|
||||||
|
// gibt es nichts zu konfigurieren – und auf dem Stellplatz gibt es
|
||||||
|
// ohnehin nichts zu tippen.
|
||||||
|
let map = CPMapTemplate()
|
||||||
|
map.mapButtons = []
|
||||||
|
map.leadingNavigationBarButtons = []
|
||||||
|
map.trailingNavigationBarButtons = []
|
||||||
|
map.hidesButtonsWithNavigationBar = true
|
||||||
|
map.mapDelegate = self
|
||||||
|
interfaceController.setRootTemplate(map, animated: false) { success, error in
|
||||||
|
if let error {
|
||||||
|
Self.log.error("setRootTemplate abgelehnt: \(error.localizedDescription, privacy: .public)")
|
||||||
|
} else {
|
||||||
|
Self.log.notice("setRootTemplate ok: \(success, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func templateApplicationScene(
|
||||||
|
_ templateApplicationScene: CPTemplateApplicationScene,
|
||||||
|
didDisconnectInterfaceController interfaceController: CPInterfaceController,
|
||||||
|
from window: CPWindow
|
||||||
|
) {
|
||||||
|
Self.log.notice("didDisconnect: Szene getrennt")
|
||||||
|
self.interfaceController = nil
|
||||||
|
window.rootViewController = nil
|
||||||
|
CarPlayLevelSource.shared.stop()
|
||||||
|
}
|
||||||
|
// MARK: - Zielteilung
|
||||||
|
|
||||||
|
// CarPlay bietet seit iOS 26 an, ein Navigationsziel an das Fahrzeug zu
|
||||||
|
// übergeben. Wir haben keine Ziele – aber das Protokoll vollständig zu
|
||||||
|
// bedienen ist Teil davon, eine korrekte Navigations-App zu sein. Der
|
||||||
|
// Verdacht dabei: Apples Template-Host löst seinen Sharing-Delegate nur
|
||||||
|
// dann sauber auf, wenn die App diese Methoden anbietet.
|
||||||
|
|
||||||
|
@available(iOS 26.4, *)
|
||||||
|
func mapTemplate(_ mapTemplate: CPMapTemplate, willShareDestinationFor trip: CPTrip) {
|
||||||
|
Self.log.notice("willShareDestination – die App teilt keine Ziele")
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 26.4, *)
|
||||||
|
func mapTemplate(_ mapTemplate: CPMapTemplate, didShareDestinationFor trip: CPTrip) {
|
||||||
|
Self.log.notice("didShareDestination")
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 26.4, *)
|
||||||
|
func mapTemplate(_ mapTemplate: CPMapTemplate, didFailToShareDestinationFor trip: CPTrip, error: Error) {
|
||||||
|
Self.log.error("didFailToShareDestination: \(error.localizedDescription, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#if CARPLAY_DEMO
|
||||||
|
import CoreLocation
|
||||||
|
import Observation
|
||||||
|
|
||||||
|
/// Sperrt die Libelle, sobald das Fahrzeug schneller als Schrittgeschwindigkeit
|
||||||
|
/// fährt.
|
||||||
|
///
|
||||||
|
/// Ausrichten passiert im Stand oder im Zentimeterbereich davor. Eine Anzeige,
|
||||||
|
/// die während der Fahrt etwas zu lesen gäbe, wäre Ablenkung ohne Nutzen – also
|
||||||
|
/// gibt es sie dort nicht. Die Grenze ist bewusst technisch erzwungen und nicht
|
||||||
|
/// bloss eine Zusage im Text.
|
||||||
|
@Observable
|
||||||
|
final class CarPlaySpeedGate: NSObject, CLLocationManagerDelegate {
|
||||||
|
static let shared = CarPlaySpeedGate()
|
||||||
|
|
||||||
|
/// Fünf km/h: schneller als Rangieren, langsamer als jede Verkehrssituation,
|
||||||
|
/// in der jemand aufs Display sehen dürfte.
|
||||||
|
static let limit: CLLocationSpeed = 5.0 / 3.6
|
||||||
|
|
||||||
|
private(set) var isStationary = true
|
||||||
|
private(set) var speed: CLLocationSpeed?
|
||||||
|
|
||||||
|
@ObservationIgnored private let manager = CLLocationManager()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
manager.delegate = self
|
||||||
|
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
// Fuer Screenshots laesst sich die Geschwindigkeit vorgeben (in km/h);
|
||||||
|
// eine simulierte GPS-Fahrt nur fuer ein Standbild waere unverhaeltnis-
|
||||||
|
// maessig. Ohne die Variable zaehlt ausschliesslich der echte Sensor.
|
||||||
|
if let forced = ProcessInfo.processInfo.environment["CARPLAY_DEMO_SPEED"].flatMap(Double.init) {
|
||||||
|
speed = forced / 3.6
|
||||||
|
isStationary = forced / 3.6 <= Self.limit
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.requestWhenInUseAuthorization()
|
||||||
|
manager.startUpdatingLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nur für die Demo-Sequenz: erzwingt den Fahrzustand, damit sich die
|
||||||
|
/// Sperre zeigen lässt, ohne eine GPS-Fahrt zu simulieren.
|
||||||
|
func setDemoDriving(_ driving: Bool) {
|
||||||
|
speed = driving ? 30 / 3.6 : 0
|
||||||
|
isStationary = !driving
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
manager.stopUpdatingLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||||
|
guard let location = locations.last else { return }
|
||||||
|
|
||||||
|
// CoreLocation meldet im Stand eine negative Geschwindigkeit: der Wert
|
||||||
|
// ist dann schlicht unbekannt. Das als "fährt" zu werten, würde die
|
||||||
|
// Funktion genau dort abschalten, wo sie gebraucht wird – auf dem
|
||||||
|
// Stellplatz, oft unter Bäumen mit schlechtem Empfang.
|
||||||
|
guard location.speed >= 0 else {
|
||||||
|
speed = nil
|
||||||
|
isStationary = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
speed = location.speed
|
||||||
|
isStationary = location.speed <= Self.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||||
|
speed = nil
|
||||||
|
isStationary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -3,21 +3,19 @@ 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, auf zwei Wegen einzuschalten:
|
/// Auf zwei Wegen einzuschalten:
|
||||||
///
|
///
|
||||||
/// * Umgebungsvariable `CAMPER_DEMO=1` beim Start – praktisch im Simulator:
|
/// * 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 \
|
/// 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.
|
|
||||||
enum DemoData {
|
enum DemoData {
|
||||||
|
|
||||||
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
|
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
|
||||||
@@ -26,10 +24,8 @@ enum DemoData {
|
|||||||
static var isEnabled: Bool {
|
static var isEnabled: Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
|
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
|
||||||
return UserDefaults.standard.bool(forKey: enabledKey)
|
|
||||||
#else
|
|
||||||
return false
|
|
||||||
#endif
|
#endif
|
||||||
|
return UserDefaults.standard.bool(forKey: enabledKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen",
|
static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct VanControlApp: App {
|
|||||||
.environment(watch)
|
.environment(watch)
|
||||||
.environment(levelActivity)
|
.environment(levelActivity)
|
||||||
.task { watch.activate() }
|
.task { watch.activate() }
|
||||||
|
.carPlayPreviewOverlay()
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase) { _, phase in
|
.onChange(of: scenePhase) { _, phase in
|
||||||
// Im Hintergrund darf ohne Service-Filter ohnehin nicht gescannt
|
// Im Hintergrund darf ohne Service-Filter ohnehin nicht gescannt
|
||||||
|
|||||||
@@ -75,9 +75,11 @@ struct LevelBubble: View {
|
|||||||
Circle()
|
Circle()
|
||||||
.fill(bubbleColor)
|
.fill(bubbleColor)
|
||||||
.frame(width: bubble, height: bubble)
|
.frame(width: bubble, height: bubble)
|
||||||
// Positiver Pitch heisst: das Heck steht höher, die Blase
|
// Draufsicht mit Fahrtrichtung oben: Positiver Pitch heisst,
|
||||||
// wandert also nach oben – in der Ansicht nach hinten.
|
// das Heck steht höher – die Blase wandert nach hinten, also
|
||||||
.offset(x: offsetX, y: -offsetY)
|
// nach unten. Wie bei einer echten Wasserwaage, die zur
|
||||||
|
// höheren Seite ausschlägt.
|
||||||
|
.offset(x: offsetX, y: offsetY)
|
||||||
.animation(.spring(duration: 0.35), value: offsetX)
|
.animation(.spring(duration: 0.35), value: offsetX)
|
||||||
.animation(.spring(duration: 0.35), value: offsetY)
|
.animation(.spring(duration: 0.35), value: offsetY)
|
||||||
.opacity(pitch == nil && roll == nil ? 0.25 : 1)
|
.opacity(pitch == nil && roll == nil ? 0.25 : 1)
|
||||||
|
|||||||
@@ -4,16 +4,13 @@ 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
|
@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 {
|
Section {
|
||||||
Toggle("Demo-Modus", isOn: $demoModeEnabled)
|
Toggle("Demo-Modus", isOn: $demoModeEnabled)
|
||||||
} header: {
|
} header: {
|
||||||
@@ -24,7 +21,6 @@ struct SettingsView: View {
|
|||||||
+ "einem Neustart der App – einmal beenden (im App-Umschalter "
|
+ "einem Neustart der App – einmal beenden (im App-Umschalter "
|
||||||
+ "nach oben wischen) und wieder öffnen.")
|
+ "nach oben wischen) und wieder öffnen.")
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ final class PhoneWatchLink: NSObject {
|
|||||||
snapshot: bluetooth.snapshots[device.id],
|
snapshot: bluetooth.snapshots[device.id],
|
||||||
level: device.role == .leveling ? bluetooth.levelStates[device.id] : nil,
|
level: device.role == .leveling ? bluetooth.levelStates[device.id] : nil,
|
||||||
orientation: device.role == .leveling ? device.sensorOrientation : nil,
|
orientation: device.role == .leveling ? device.sensorOrientation : nil,
|
||||||
|
vehicleGraphicStyle: device.role == .leveling ? device.vehicleGraphicStyle : nil,
|
||||||
fridge: device.role == .fridge ? fridgeState(for: device) : nil)
|
fridge: device.role == .fridge ? fridgeState(for: device) : nil)
|
||||||
}
|
}
|
||||||
return WatchPayload(generatedAt: Date(),
|
return WatchPayload(generatedAt: Date(),
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 326 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "CaliforniaRear.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 },
|
||||||
|
"properties" : { "template-rendering-intent" : "template" }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 311 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "CaliforniaSide.png",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 },
|
||||||
|
"properties" : { "template-rendering-intent" : "template" }
|
||||||
|
}
|
||||||
@@ -30,10 +30,11 @@ struct WatchBubble: View {
|
|||||||
Circle()
|
Circle()
|
||||||
.fill(color)
|
.fill(color)
|
||||||
.frame(width: bubble, height: bubble)
|
.frame(width: bubble, height: bubble)
|
||||||
// Positiver Pitch heisst: das Heck steht höher, die Blase
|
// Draufsicht mit Fahrtrichtung oben: Positiver Pitch heisst,
|
||||||
// wandert nach oben – in der Ansicht also nach hinten.
|
// das Heck steht höher – die Blase wandert nach hinten, also
|
||||||
|
// nach unten.
|
||||||
.offset(x: clamped(roll) / range * travel,
|
.offset(x: clamped(roll) / range * travel,
|
||||||
y: -clamped(pitch) / range * travel)
|
y: clamped(pitch) / range * travel)
|
||||||
.animation(.spring(duration: 0.3), value: pitch)
|
.animation(.spring(duration: 0.3), value: pitch)
|
||||||
.animation(.spring(duration: 0.3), value: roll)
|
.animation(.spring(duration: 0.3), value: roll)
|
||||||
.opacity(pitch == nil && roll == nil ? 0.25 : 1)
|
.opacity(pitch == nil && roll == nil ? 0.25 : 1)
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ struct LevelReadingPage: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
if showsVehicle {
|
if showsVehicle {
|
||||||
WatchVehicleView(pitch: reading.state.pitch, roll: reading.state.roll)
|
WatchVehicleView(pitch: reading.state.pitch, roll: reading.state.roll,
|
||||||
|
style: link.payload?.levelDevice?.vehicleGraphicStyle ?? .vanster)
|
||||||
} else {
|
} else {
|
||||||
WatchBubble(pitch: reading.state.pitch, roll: reading.state.roll)
|
WatchBubble(pitch: reading.state.pitch, roll: reading.state.roll)
|
||||||
.frame(maxWidth: 110)
|
.frame(maxWidth: 110)
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ import SwiftUI
|
|||||||
struct WatchVehicleView: View {
|
struct WatchVehicleView: View {
|
||||||
let pitch: Double?
|
let pitch: Double?
|
||||||
let roll: Double?
|
let roll: Double?
|
||||||
|
/// Vom iPhone übernommen – ausgewählt wird sie nur dort, siehe
|
||||||
|
/// `WatchDevice.vehicleGraphicStyle`.
|
||||||
|
var style: VehicleGraphicStyle = .vanster
|
||||||
|
|
||||||
/// Wie am iPhone dreifach überhöht – zwei Grad wären sonst kaum zu sehen.
|
/// Wie am iPhone dreifach überhöht – zwei Grad wären sonst kaum zu sehen.
|
||||||
/// Die angeschriebenen Gradzahlen bleiben echt.
|
/// Die angeschriebenen Gradzahlen bleiben echt.
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
panel(image: "VansterSide", angle: pitch, title: "Längs",
|
panel(image: style.sideImageName, angle: pitch, title: "Längs",
|
||||||
lower: "Front", upper: "Heck", aspect: VehicleTilt.sideAspect)
|
lower: "Front", upper: "Heck", aspect: style.sideAspect)
|
||||||
panel(image: "VansterRear", angle: roll, title: "Quer",
|
panel(image: style.rearImageName, angle: roll, title: "Quer",
|
||||||
lower: "links", upper: "rechts", aspect: VehicleTilt.rearAspect)
|
lower: "links", upper: "rechts", aspect: style.rearAspect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,15 +63,17 @@ enum WatchDemo {
|
|||||||
devices: [
|
devices: [
|
||||||
WatchDevice(id: levelID, name: "Nivellierung", role: .leveling,
|
WatchDevice(id: levelID, name: "Nivellierung", role: .leveling,
|
||||||
link: .live, snapshot: level.snapshot(deviceID: levelID, rssi: -70),
|
link: .live, snapshot: level.snapshot(deviceID: levelID, rssi: -70),
|
||||||
level: level, orientation: .identity, fridge: nil),
|
level: level, orientation: .identity,
|
||||||
|
vehicleGraphicStyle: .vanster, fridge: nil),
|
||||||
WatchDevice(id: batteryID, name: "Bulltron 200 Ah", role: .bms,
|
WatchDevice(id: batteryID, name: "Bulltron 200 Ah", role: .bms,
|
||||||
link: .live, snapshot: battery, level: nil,
|
link: .live, snapshot: battery, level: nil,
|
||||||
orientation: nil, fridge: nil),
|
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
|
||||||
WatchDevice(id: victronSolarID, name: "Solar Dach", role: .victronSolarCharger,
|
WatchDevice(id: victronSolarID, name: "Solar Dach", role: .victronSolarCharger,
|
||||||
link: .live, snapshot: victronSolar, level: nil,
|
link: .live, snapshot: victronSolar, level: nil,
|
||||||
orientation: nil, fridge: nil),
|
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
|
||||||
WatchDevice(id: fridgeID, name: "Kühlbox", role: .fridge,
|
WatchDevice(id: fridgeID, name: "Kühlbox", role: .fridge,
|
||||||
link: .live, snapshot: fridge, level: nil, orientation: nil,
|
link: .live, snapshot: fridge, level: nil, orientation: nil,
|
||||||
|
vehicleGraphicStyle: nil,
|
||||||
fridge: WatchFridge(isLive: true, updated: now,
|
fridge: WatchFridge(isLive: true, updated: now,
|
||||||
isPoweredOn: true, isEco: true, isLocked: false,
|
isPoweredOn: true, isEco: true, isLocked: false,
|
||||||
isDualZone: false, unitSymbol: "°C",
|
isDualZone: false, unitSymbol: "°C",
|
||||||
|
|||||||