Auffahrkeile: Höhe je Rad statt getrennt für quer und längs
Bisher standen dort zwei Angaben nebeneinander, etwa "rechts 8 cm" und "vorne 4 cm". Das liest sich wie zwei Aufgaben, ist aber eine: Unter "rechts" liegen zwei Räder, unter "vorne" auch, und eines davon ist dasselbe. Ausführen lässt sich das so nicht. Gerechnet wird jetzt über alle vier Aufstandspunkte auf einmal. Aus Spurweite, Radstand und den beiden Winkeln ergeben sich deren Höhen; angehoben wird auf das höchststehende Rad, das liegen bleibt. Aus dem Beispiel oben werden damit 12 cm vorne rechts, 8 cm hinten rechts, 4 cm vorne links und nichts hinten links – dieselbe Geometrie, nur richtig zusammengesetzt. Angezeigt wird das als Draufsicht auf das Fahrzeug (WheelLiftPlan), auf iPhone und Uhr dieselbe Ansicht, auf der Uhr kompakt. LevelingWedge entfällt. Abgesichert in run-tests.sh: reine Querneigung, reine Längsneigung, beides zusammen (16,1 / 9,2 / 7,0 / 0 cm), gespiegelte Vorzeichen und die Fälle ohne Fahrzeugmasse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,37 +176,37 @@ struct AlignmentAssistantView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var wedgeSection: some View {
|
private var wedgeSection: some View {
|
||||||
let across = profile?.trackWidth.flatMap { width in
|
// Gerechnet wird über alle vier Räder auf einmal. Getrennte Angaben für
|
||||||
state.roll.flatMap { LevelingWedge.across(roll: $0, trackWidth: width) }
|
// quer und längs beschreiben dasselbe Fahrzeug und lassen sich nicht
|
||||||
}
|
// getrennt ausführen: Unter „rechts" liegen zwei Räder, unter „vorne"
|
||||||
let along = profile?.wheelbase.flatMap { base in
|
// auch, und eines davon ist dasselbe.
|
||||||
state.pitch.flatMap { LevelingWedge.along(pitch: $0, wheelbase: base) }
|
let lift: LevelingLift? = {
|
||||||
}
|
guard let width = profile?.trackWidth, let base = profile?.wheelbase,
|
||||||
|
let pitch = state.pitch, let roll = state.roll else { return nil }
|
||||||
|
return LevelingLift.compute(pitch: pitch, roll: roll,
|
||||||
|
trackWidth: width, wheelbase: base)
|
||||||
|
}()
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Label("Auffahrkeile", systemImage: "triangle.fill")
|
Label("Auffahrkeile", systemImage: "triangle.fill")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
||||||
if profile?.trackWidth == nil && profile?.wheelbase == nil {
|
if profile?.trackWidth == nil || profile?.wheelbase == nil {
|
||||||
Text("Für die Keilhöhe fehlen Spurweite und Radstand. Beides lässt "
|
Text("Für die Keilhöhe fehlen Spurweite und Radstand. Beides lässt "
|
||||||
+ "sich beim Fahrzeug hinterlegen.")
|
+ "sich beim Fahrzeug hinterlegen.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
} else if across == nil && along == nil {
|
} else if let lift, !lift.isNegligible {
|
||||||
Text("Keine Keile nötig.")
|
WheelLiftPlan(lift: lift)
|
||||||
.font(.callout)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
Text("Zentimeter unter das jeweilige Rad. Das höchststehende Rad "
|
||||||
|
+ "bleibt liegen, die übrigen werden auf seine Höhe gebracht.")
|
||||||
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
} else {
|
} else {
|
||||||
ForEach([across, along].compactMap { $0 }, id: \.side) { wedge in
|
Text("Keine Keile nötig.")
|
||||||
HStack {
|
.font(.callout)
|
||||||
Text(wedge.side.text.capitalized)
|
|
||||||
Spacer()
|
|
||||||
Text(String(format: "%.0f cm", wedge.heightInCentimetres))
|
|
||||||
.font(.title3.weight(.semibold).monospacedDigit())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text("Höhe unter die tieferstehende Seite, damit das Fahrzeug eben steht.")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,4 +230,3 @@ struct AlignmentAssistantView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LevelingWedge.Side: Hashable {}
|
|
||||||
|
|||||||
@@ -36,11 +36,9 @@ struct WatchLevelView: View {
|
|||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundStyle(reading.state.isLevel ? .green : .primary)
|
.foregroundStyle(reading.state.isLevel ? .green : .primary)
|
||||||
|
|
||||||
if let wedges = wedgeText {
|
if let lift, !lift.isNegligible {
|
||||||
Text(wedges)
|
WheelLiftPlan(lift: lift, isCompact: true)
|
||||||
.font(.caption2)
|
.padding(.top, 2)
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
@@ -76,20 +74,15 @@ struct WatchLevelView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wie hoch die Auffahrkeile sein müssen – reine Geometrie, und nur zu
|
/// Wie hoch jedes Rad unterlegt werden muss – reine Geometrie, und nur zu
|
||||||
/// haben, wenn im Fahrzeugprofil Spurweite und Radstand stehen.
|
/// haben, wenn im Fahrzeugprofil Spurweite und Radstand stehen.
|
||||||
private var wedgeText: String? {
|
private var lift: LevelingLift? {
|
||||||
guard let profile = link.payload?.profile else { return nil }
|
guard let profile = link.payload?.profile,
|
||||||
var parts: [String] = []
|
let width = profile.trackWidth, let base = profile.wheelbase,
|
||||||
if let roll = reading.state.roll, let width = profile.trackWidth,
|
let pitch = reading.state.pitch, let roll = reading.state.roll
|
||||||
let wedge = LevelingWedge.across(roll: roll, trackWidth: width) {
|
else { return nil }
|
||||||
parts.append(String(format: "%.0f cm %@", wedge.heightInCentimetres, wedge.side.text))
|
return LevelingLift.compute(pitch: pitch, roll: roll,
|
||||||
}
|
trackWidth: width, wheelbase: base)
|
||||||
if let pitch = reading.state.pitch, let base = profile.wheelbase,
|
|
||||||
let wedge = LevelingWedge.along(pitch: pitch, wheelbase: base) {
|
|
||||||
parts.append(String(format: "%.0f cm %@", wedge.heightInCentimetres, wedge.side.text))
|
|
||||||
}
|
|
||||||
return parts.isEmpty ? nil : "Keile: " + parts.joined(separator: ", ")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,10 +205,16 @@ Das steckt vollständig im zeitlichen Verlauf der Neigung – ohne jede Annahme
|
|||||||
über das Gelände.
|
über das Gelände.
|
||||||
|
|
||||||
Sind beim Fahrzeug **Spurweite und Radstand** hinterlegt, rechnet der Assistent
|
Sind beim Fahrzeug **Spurweite und Radstand** hinterlegt, rechnet der Assistent
|
||||||
zusätzlich die nötige Höhe der Auffahrkeile aus. Das ist reine Geometrie und
|
zusätzlich die nötige Höhe der Auffahrkeile aus – **je Rad**, als Draufsicht auf
|
||||||
damit exakt: 2,0° Querneigung bei 2,00 m Spurweite ergeben 7,0 cm unter die
|
das Fahrzeug. Getrennte Angaben für quer und längs wären irreführend: „rechts
|
||||||
tieferstehende Seite. Die Maße stehen im Fahrzeugprofil, erreichbar über das
|
8 cm" und „vorne 4 cm" beschreiben dasselbe Fahrzeug, und unter beiden liegt
|
||||||
ⓘ in der Fahrzeugliste.
|
teils dasselbe Rad. Es steht auf vier Punkten, also gehören vier Zahlen hin.
|
||||||
|
|
||||||
|
Das ist reine Geometrie und damit exakt: 2,0° Querneigung bei 2,00 m Spurweite
|
||||||
|
ergeben 7,0 cm unter beide Räder der tieferstehenden Seite; kommen 1,5°
|
||||||
|
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
|
||||||
|
stehen im Fahrzeugprofil, erreichbar über das ⓘ in der Fahrzeugliste.
|
||||||
|
|
||||||
## Apple Watch
|
## Apple Watch
|
||||||
|
|
||||||
@@ -219,7 +225,7 @@ den Arm hebt:
|
|||||||
Verbindung. Ganz unten steht, wie alt der Stand ist.
|
Verbindung. Ganz unten steht, wie alt der Stand ist.
|
||||||
* **Nivellierung** – wahlweise als Fahrzeugansicht wie am iPhone (Seiten- und
|
* **Nivellierung** – wahlweise als Fahrzeugansicht wie am iPhone (Seiten- und
|
||||||
Heckansicht, dreifach überhöht) oder als Libelle; umschaltbar und über Starts
|
Heckansicht, dreifach überhöht) oder als Libelle; umschaltbar und über Starts
|
||||||
hinweg gemerkt. Dazu, was zu tun ist, und die nötige Keilhöhe, falls im
|
hinweg gemerkt. Dazu, was zu tun ist, und die Keilhöhe je Rad, falls im
|
||||||
Fahrzeugprofil Spurweite und Radstand stehen. Kalibriert wird nur am iPhone:
|
Fahrzeugprofil Spurweite und Radstand stehen. Kalibriert wird nur am iPhone:
|
||||||
Das gehört einmalig auf ebenen Boden, und ein Knopf dafür an der Uhr wäre vor
|
Das gehört einmalig auf ebenen Boden, und ein Knopf dafür an der Uhr wäre vor
|
||||||
allem eine Gelegenheit, die Nullage aus Versehen zu verstellen.
|
allem eine Gelegenheit, die Nullage aus Versehen zu verstellen.
|
||||||
@@ -328,7 +334,7 @@ firmware/vanalign/ Firmware des Neigungsmessers (ESPHome)
|
|||||||
Shared/ In iPhone- und Watch-App übersetzt
|
Shared/ In iPhone- und Watch-App übersetzt
|
||||||
├── Models/
|
├── Models/
|
||||||
│ ├── Profile.swift Fahrzeug samt Maßen
|
│ ├── Profile.swift Fahrzeug samt Maßen
|
||||||
│ ├── AlignmentAssistant.swift Verlauf, Tendenz und Keilberechnung
|
│ ├── AlignmentAssistant.swift Verlauf, Tendenz und Keilhöhe je Rad
|
||||||
│ ├── SensorOrientation.swift Einbaulage und ihre Erkennung
|
│ ├── SensorOrientation.swift Einbaulage und ihre Erkennung
|
||||||
│ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart
|
│ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart
|
||||||
│ ├── DeviceSnapshot.swift Messwerte in Anzeigeform
|
│ ├── DeviceSnapshot.swift Messwerte in Anzeigeform
|
||||||
@@ -338,6 +344,7 @@ Shared/ In iPhone- und Watch-App übersetzt
|
|||||||
│ ├── 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)
|
||||||
├── 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
|
||||||
└── WatchLink/
|
└── WatchLink/
|
||||||
└── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr
|
└── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr
|
||||||
|
|
||||||
|
|||||||
@@ -129,55 +129,85 @@ struct AlignmentAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wie hoch ein Auffahrkeil sein muss, um eine Neigung auszugleichen.
|
/// Wie hoch jedes einzelne Rad unterlegt werden muss.
|
||||||
///
|
///
|
||||||
/// Rein geometrisch und damit exakt: Höhe = tan(Winkel) × Abstand der Achsen
|
/// Getrennte Angaben wie „rechts 8 cm" und „vorne 4 cm" beschreiben dasselbe
|
||||||
/// beziehungsweise der Räder.
|
/// Fahrzeug und lassen sich nicht getrennt ausführen: Unter „rechts" liegen
|
||||||
struct LevelingWedge: Equatable {
|
/// zwei Räder, unter „vorne" auch, und zwei davon sind dieselben. Gefragt ist
|
||||||
/// Wo der Keil hin muss.
|
/// deshalb die Höhe **je Rad**.
|
||||||
let side: Side
|
///
|
||||||
/// Höhe in Metern.
|
/// Das ist reine Geometrie und damit exakt. Das Fahrzeug steht auf vier
|
||||||
let height: Double
|
/// Punkten; deren Höhen ergeben sich aus Spurweite, Radstand und den beiden
|
||||||
/// Der zugrundeliegende Winkel in Grad.
|
/// Winkeln. Angehoben wird auf die Höhe des höchsten Rades – das bleibt
|
||||||
let angle: Double
|
/// liegen, die anderen bekommen die Differenz.
|
||||||
|
struct LevelingLift: Equatable {
|
||||||
|
|
||||||
enum Side: Equatable {
|
enum Wheel: CaseIterable, Hashable {
|
||||||
case front, rear, left, right
|
case frontLeft, frontRight, rearLeft, rearRight
|
||||||
|
|
||||||
var text: String {
|
var text: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .front: return "vorne"
|
case .frontLeft: return "vorne links"
|
||||||
case .rear: return "hinten"
|
case .frontRight: return "vorne rechts"
|
||||||
case .left: return "links"
|
case .rearLeft: return "hinten links"
|
||||||
case .right: return "rechts"
|
case .rearRight: return "hinten rechts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var heightInCentimetres: Double { height * 100 }
|
/// Höhen in Metern, je Rad. Das höchste Rad steht auf 0.
|
||||||
|
let frontLeft: Double
|
||||||
|
let frontRight: Double
|
||||||
|
let rearLeft: Double
|
||||||
|
let rearRight: Double
|
||||||
|
|
||||||
/// Quer: die tieferliegende Seite muss angehoben werden. Positiver Roll
|
func height(_ wheel: Wheel) -> Double {
|
||||||
/// heisst, dass rechts höher steht – der Keil gehört also nach links.
|
switch wheel {
|
||||||
static func across(roll: Double, trackWidth: Double) -> LevelingWedge? {
|
case .frontLeft: return frontLeft
|
||||||
wedge(angle: roll, distance: trackWidth,
|
case .frontRight: return frontRight
|
||||||
whenPositive: .left, whenNegative: .right)
|
case .rearLeft: return rearLeft
|
||||||
|
case .rearRight: return rearRight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Längs: positiver Pitch heisst, dass das Heck höher steht – der Keil
|
func centimetres(_ wheel: Wheel) -> Double { height(wheel) * 100 }
|
||||||
/// gehört unter die Vorderräder.
|
|
||||||
static func along(pitch: Double, wheelbase: Double) -> LevelingWedge? {
|
/// Das am tiefsten stehende Rad – dort liegt die grösste Höhe.
|
||||||
wedge(angle: pitch, distance: wheelbase,
|
var deepest: Wheel {
|
||||||
whenPositive: .front, whenNegative: .rear)
|
Wheel.allCases.max { height($0) < height($1) } ?? .frontLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func wedge(angle: Double,
|
var maximumCentimetres: Double { centimetres(deepest) }
|
||||||
distance: Double,
|
|
||||||
whenPositive: Side,
|
/// Unter einem Zentimeter lohnt kein Keil; so genau steht kein Fahrzeug,
|
||||||
whenNegative: Side) -> LevelingWedge? {
|
/// und so genau misst der Sensor auch nicht.
|
||||||
guard distance > 0, abs(angle) > LevelState.levelTolerance else { return nil }
|
var isNegligible: Bool { maximumCentimetres < 1 }
|
||||||
let height = tan(abs(angle) * .pi / 180) * distance
|
|
||||||
return LevelingWedge(side: angle > 0 ? whenPositive : whenNegative,
|
/// `pitch` positiv heisst, das Heck steht höher; `roll` positiv, die rechte
|
||||||
height: height,
|
/// Seite steht höher. Ohne Fahrzeugmasse gibt es nichts zu rechnen.
|
||||||
angle: abs(angle))
|
static func compute(pitch: Double,
|
||||||
|
roll: Double,
|
||||||
|
trackWidth: Double,
|
||||||
|
wheelbase: Double) -> LevelingLift? {
|
||||||
|
guard trackWidth > 0, wheelbase > 0 else { return nil }
|
||||||
|
|
||||||
|
// Wie viel höher die rechte Seite und wie viel höher das Heck steht.
|
||||||
|
// Vorzeichen inbegriffen: tan(-2°) ist negativ, dann steht links höher.
|
||||||
|
let rightRise = tan(roll * .pi / 180) * trackWidth
|
||||||
|
let rearRise = tan(pitch * .pi / 180) * wheelbase
|
||||||
|
|
||||||
|
// Höhen der vier Aufstandspunkte, bezogen auf vorne links.
|
||||||
|
let heights: [Wheel: Double] = [
|
||||||
|
.frontLeft: 0,
|
||||||
|
.frontRight: rightRise,
|
||||||
|
.rearLeft: rearRise,
|
||||||
|
.rearRight: rightRise + rearRise,
|
||||||
|
]
|
||||||
|
let top = heights.values.max() ?? 0
|
||||||
|
|
||||||
|
return LevelingLift(frontLeft: top - heights[.frontLeft]!,
|
||||||
|
frontRight: top - heights[.frontRight]!,
|
||||||
|
rearLeft: top - heights[.rearLeft]!,
|
||||||
|
rearRight: top - heights[.rearRight]!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Die Keilhöhen als Draufsicht auf das Fahrzeug.
|
||||||
|
///
|
||||||
|
/// Vier Zahlen an vier Rädern, statt zweier Angaben „rechts" und „vorne", die
|
||||||
|
/// sich nicht getrennt ausführen lassen. Oben ist vorne; links im Bild ist
|
||||||
|
/// links am Fahrzeug, aus Sicht des Fahrers.
|
||||||
|
///
|
||||||
|
/// Das höchststehende Rad bekommt nichts – es bleibt liegen, die anderen
|
||||||
|
/// werden auf seine Höhe gebracht.
|
||||||
|
struct WheelLiftPlan: View {
|
||||||
|
let lift: LevelingLift
|
||||||
|
/// Für die Uhr: kleinere Schrift, engerer Aufbau.
|
||||||
|
var isCompact = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: isCompact ? 2 : 6) {
|
||||||
|
Text("vorne")
|
||||||
|
.font(labelFont)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
HStack(spacing: isCompact ? 6 : 12) {
|
||||||
|
VStack(spacing: isCompact ? 6 : 14) {
|
||||||
|
badge(.frontLeft)
|
||||||
|
badge(.rearLeft)
|
||||||
|
}
|
||||||
|
body_
|
||||||
|
VStack(spacing: isCompact ? 6 : 14) {
|
||||||
|
badge(.frontRight)
|
||||||
|
badge(.rearRight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("hinten")
|
||||||
|
.font(labelFont)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
.accessibilityLabel(accessibilityText)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Der Umriss dazwischen macht aus vier Zahlen ein Fahrzeug.
|
||||||
|
private var body_: some View {
|
||||||
|
RoundedRectangle(cornerRadius: isCompact ? 4 : 8)
|
||||||
|
.strokeBorder(Color.secondary.opacity(0.4), lineWidth: 1)
|
||||||
|
.frame(width: isCompact ? 26 : 48, height: isCompact ? 44 : 84)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func badge(_ wheel: LevelingLift.Wheel) -> some View {
|
||||||
|
let value = lift.centimetres(wheel)
|
||||||
|
let needed = value >= 1
|
||||||
|
return Text(needed ? String(format: "%.0f", value) : "–")
|
||||||
|
.font(valueFont)
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(needed ? .primary : .secondary)
|
||||||
|
.frame(width: isCompact ? 26 : 42, height: isCompact ? 20 : 30)
|
||||||
|
.background(needed ? Color.orange.opacity(0.25) : Color.secondary.opacity(0.12),
|
||||||
|
in: .rect(cornerRadius: isCompact ? 4 : 6))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var labelFont: Font { isCompact ? .system(size: 9) : .caption2 }
|
||||||
|
private var valueFont: Font {
|
||||||
|
isCompact ? .system(size: 12, weight: .semibold) : .callout.weight(.semibold)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var accessibilityText: String {
|
||||||
|
let parts = LevelingLift.Wheel.allCases.compactMap { wheel -> String? in
|
||||||
|
let value = lift.centimetres(wheel)
|
||||||
|
guard value >= 1 else { return nil }
|
||||||
|
return String(format: "%@ %.0f Zentimeter", wheel.text, value)
|
||||||
|
}
|
||||||
|
return parts.isEmpty ? "Keine Keile nötig" : "Unterlegen: " + parts.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-19
@@ -826,28 +826,53 @@ ageing.add(pitch: 1, roll: 0, at: start.addingTimeInterval(AlignmentAssistant.me
|
|||||||
checkEqual("alte Messwerte fallen heraus", ageing.samples.count, 1)
|
checkEqual("alte Messwerte fallen heraus", ageing.samples.count, 1)
|
||||||
|
|
||||||
// MARK: Auffahrkeile
|
// MARK: Auffahrkeile
|
||||||
print("\nAuffahrkeile")
|
|
||||||
|
|
||||||
// tan(2°) x 2,00 m = 6,99 cm
|
print("\nAuffahrkeile – Höhe je Rad")
|
||||||
let acrossWedge = LevelingWedge.across(roll: 2.0, trackWidth: 2.0)
|
|
||||||
checkEqual("Keilhöhe quer",
|
|
||||||
acrossWedge.map { ($0.heightInCentimetres * 10).rounded() / 10 }, 7.0)
|
|
||||||
checkEqual("rechts höher heisst Keil nach links", acrossWedge?.side, .left)
|
|
||||||
checkEqual("links höher heisst Keil nach rechts",
|
|
||||||
LevelingWedge.across(roll: -2.0, trackWidth: 2.0)?.side, .right)
|
|
||||||
|
|
||||||
// tan(1,5°) x 3,50 m = 9,16 cm
|
// Reine Querneigung: tan(2°) x 2,00 m = 6,99 cm unter beide Räder der
|
||||||
let alongWedge = LevelingWedge.along(pitch: 1.5, wheelbase: 3.5)
|
// tieferstehenden Seite. Rechts steht höher, also muss links hoch.
|
||||||
checkEqual("Keilhöhe längs",
|
let acrossOnly = LevelingLift.compute(pitch: 0, roll: 2.0, trackWidth: 2.0, wheelbase: 3.5)
|
||||||
alongWedge.map { ($0.heightInCentimetres * 10).rounded() / 10 }, 9.2)
|
checkEqual("quer: vorne links",
|
||||||
checkEqual("Heck höher heisst Keil nach vorne", alongWedge?.side, .front)
|
acrossOnly.map { ($0.centimetres(.frontLeft) * 10).rounded() / 10 }, 7.0)
|
||||||
checkEqual("Front höher heisst Keil nach hinten",
|
checkEqual("quer: hinten links",
|
||||||
LevelingWedge.along(pitch: -1.5, wheelbase: 3.5)?.side, .rear)
|
acrossOnly.map { ($0.centimetres(.rearLeft) * 10).rounded() / 10 }, 7.0)
|
||||||
|
checkEqual("quer: rechts bleibt liegen",
|
||||||
|
acrossOnly.map { $0.centimetres(.frontRight) + $0.centimetres(.rearRight) }, 0)
|
||||||
|
|
||||||
checkEqual("innerhalb der Toleranz kein Keil",
|
// Reine Längsneigung: tan(1,5°) x 3,50 m = 9,16 cm unter beide Vorderräder,
|
||||||
LevelingWedge.across(roll: 0.3, trackWidth: 2.0) == nil, true)
|
// wenn das Heck höher steht.
|
||||||
checkEqual("ohne Maß kein Keil",
|
let alongOnly = LevelingLift.compute(pitch: 1.5, roll: 0, trackWidth: 2.0, wheelbase: 3.5)
|
||||||
LevelingWedge.across(roll: 3.0, trackWidth: 0) == nil, true)
|
checkEqual("längs: vorne links",
|
||||||
|
alongOnly.map { ($0.centimetres(.frontLeft) * 10).rounded() / 10 }, 9.2)
|
||||||
|
checkEqual("längs: vorne rechts",
|
||||||
|
alongOnly.map { ($0.centimetres(.frontRight) * 10).rounded() / 10 }, 9.2)
|
||||||
|
checkEqual("längs: hinten bleibt liegen",
|
||||||
|
alongOnly.map { $0.centimetres(.rearLeft) + $0.centimetres(.rearRight) }, 0)
|
||||||
|
|
||||||
|
// Beides zusammen: Das tiefste Rad braucht die Summe, das höchste nichts, und
|
||||||
|
// die beiden dazwischen je einen Anteil. Genau das lässt sich mit getrennten
|
||||||
|
// Angaben für "rechts" und "vorne" nicht ausdrücken.
|
||||||
|
let liftBoth = LevelingLift.compute(pitch: 1.5, roll: 2.0, trackWidth: 2.0, wheelbase: 3.5)
|
||||||
|
checkEqual("kombiniert: tiefstes Rad ist vorne links", liftBoth?.deepest, .frontLeft)
|
||||||
|
checkEqual("kombiniert: vorne links trägt beide Anteile",
|
||||||
|
liftBoth.map { ($0.centimetres(.frontLeft) * 10).rounded() / 10 }, 16.1)
|
||||||
|
checkEqual("kombiniert: vorne rechts nur den Längsanteil",
|
||||||
|
liftBoth.map { ($0.centimetres(.frontRight) * 10).rounded() / 10 }, 9.2)
|
||||||
|
checkEqual("kombiniert: hinten links nur den Queranteil",
|
||||||
|
liftBoth.map { ($0.centimetres(.rearLeft) * 10).rounded() / 10 }, 7.0)
|
||||||
|
checkEqual("kombiniert: hinten rechts steht am höchsten und bleibt liegen",
|
||||||
|
liftBoth.map { $0.centimetres(.rearRight) }, 0)
|
||||||
|
|
||||||
|
// Vorzeichen: andersherum geneigt, andere Ecke.
|
||||||
|
let mirrored = LevelingLift.compute(pitch: -1.5, roll: -2.0, trackWidth: 2.0, wheelbase: 3.5)
|
||||||
|
checkEqual("umgekehrt geneigt: tiefstes Rad ist hinten rechts",
|
||||||
|
mirrored?.deepest, .rearRight)
|
||||||
|
|
||||||
|
checkEqual("fast eben: kein Keil nötig",
|
||||||
|
LevelingLift.compute(pitch: 0.1, roll: 0.1,
|
||||||
|
trackWidth: 2.0, wheelbase: 3.5)?.isNegligible, true)
|
||||||
|
checkEqual("ohne Fahrzeugmasse keine Rechnung",
|
||||||
|
LevelingLift.compute(pitch: 2, roll: 2, trackWidth: 0, wheelbase: 3.5) == nil, true)
|
||||||
|
|
||||||
// MARK: 12 – Einbaulage des Neigungsmessers
|
// MARK: 12 – Einbaulage des Neigungsmessers
|
||||||
print("\nEinbaulage")
|
print("\nEinbaulage")
|
||||||
|
|||||||
Reference in New Issue
Block a user